Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- frontend/src/pages/revision/ 폴더 완전 삭제 - EnhancedRevisionPage.css 제거 - support_details 저장 시 트랜잭션 오류로 인해 임시로 상세 정보 저장 비활성화 - 리비전 기능 재설계 예정
279 lines
9.3 KiB
Python
279 lines
9.3 KiB
Python
"""
|
|
간단한 리비전 관리 API
|
|
복잡한 dependency 없이 핵심 기능만 제공
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File as FastAPIFile
|
|
from sqlalchemy.orm import Session
|
|
from typing import Dict, List, Any, Optional
|
|
import json
|
|
|
|
from ..database import get_db
|
|
from ..services.simple_revision_service import SimpleRevisionService
|
|
from ..services.file_upload_service import FileUploadService
|
|
from ..models import File, Material
|
|
from ..utils.logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
router = APIRouter(prefix="/simple-revision", tags=["Simple Revision"])
|
|
|
|
@router.post("/compare")
|
|
async def compare_revisions(
|
|
current_file_id: int,
|
|
previous_file_id: int,
|
|
category: str,
|
|
username: str = "system",
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
두 리비전 간의 자재 비교
|
|
|
|
Args:
|
|
current_file_id: 현재 파일 ID
|
|
previous_file_id: 이전 파일 ID
|
|
category: 비교할 카테고리 (PIPE, FITTING, FLANGE, VALVE, GASKET, BOLT, SUPPORT, SPECIAL, UNCLASSIFIED)
|
|
username: 비교 수행자
|
|
"""
|
|
try:
|
|
service = SimpleRevisionService(db)
|
|
result = service.compare_revisions(
|
|
current_file_id, previous_file_id, category, username
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"{category} 카테고리 리비전 비교 완료",
|
|
"data": result
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"리비전 비교 실패: {e}")
|
|
raise HTTPException(status_code=500, detail=f"리비전 비교 실패: {str(e)}")
|
|
|
|
@router.get("/comparison/{comparison_id}")
|
|
async def get_comparison_result(
|
|
comparison_id: int,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""저장된 비교 결과 조회"""
|
|
try:
|
|
service = SimpleRevisionService(db)
|
|
result = service.get_comparison_result(comparison_id)
|
|
|
|
if not result:
|
|
raise HTTPException(status_code=404, detail="비교 결과를 찾을 수 없습니다")
|
|
|
|
return {
|
|
"success": True,
|
|
"data": result
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"비교 결과 조회 실패: {e}")
|
|
raise HTTPException(status_code=500, detail=f"비교 결과 조회 실패: {str(e)}")
|
|
|
|
@router.post("/upload-revision")
|
|
async def upload_revision_file(
|
|
file: UploadFile = FastAPIFile(...),
|
|
job_no: str = "default",
|
|
previous_file_id: int = None,
|
|
username: str = "system",
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
리비전 파일 업로드 및 자동 비교
|
|
|
|
Args:
|
|
file: 업로드할 BOM 파일
|
|
job_no: 작업 번호
|
|
previous_file_id: 비교할 이전 파일 ID
|
|
username: 업로드 사용자
|
|
"""
|
|
try:
|
|
# 1. 파일 업로드
|
|
upload_service = FileUploadService(db)
|
|
upload_result = await upload_service.process_upload(
|
|
file=file,
|
|
job_no=job_no,
|
|
username=username,
|
|
is_revision=True
|
|
)
|
|
|
|
current_file_id = upload_result['file_id']
|
|
|
|
# 2. 이전 파일이 지정되지 않은 경우 최신 파일 찾기
|
|
if not previous_file_id:
|
|
previous_file = db.query(File).filter(
|
|
File.job_no == job_no,
|
|
File.id != current_file_id,
|
|
File.is_active == True
|
|
).order_by(File.upload_date.desc()).first()
|
|
|
|
if previous_file:
|
|
previous_file_id = previous_file.id
|
|
else:
|
|
return {
|
|
"success": True,
|
|
"message": "첫 번째 파일이므로 비교할 이전 파일이 없습니다",
|
|
"data": {
|
|
"file_id": current_file_id,
|
|
"comparison_results": []
|
|
}
|
|
}
|
|
|
|
# 3. 각 카테고리별로 자동 비교 수행
|
|
categories = [
|
|
'PIPE', 'FITTING', 'FLANGE', 'VALVE',
|
|
'GASKET', 'BOLT', 'SUPPORT', 'SPECIAL', 'UNCLASSIFIED'
|
|
]
|
|
|
|
revision_service = SimpleRevisionService(db)
|
|
comparison_results = []
|
|
|
|
for category in categories:
|
|
try:
|
|
# 해당 카테고리에 자재가 있는지 확인
|
|
current_materials = db.query(Material).filter(
|
|
Material.file_id == current_file_id,
|
|
Material.classified_category == category
|
|
).count()
|
|
|
|
previous_materials = db.query(Material).filter(
|
|
Material.file_id == previous_file_id,
|
|
Material.classified_category == category
|
|
).count()
|
|
|
|
if current_materials > 0 or previous_materials > 0:
|
|
# 비교 수행
|
|
comparison_result = revision_service.compare_revisions(
|
|
current_file_id, previous_file_id, category, username
|
|
)
|
|
comparison_results.append(comparison_result)
|
|
|
|
except Exception as e:
|
|
logger.warning(f"{category} 카테고리 비교 실패: {e}")
|
|
continue
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"리비전 파일 업로드 및 비교 완료 ({len(comparison_results)}개 카테고리)",
|
|
"data": {
|
|
"file_id": current_file_id,
|
|
"previous_file_id": previous_file_id,
|
|
"comparison_results": comparison_results
|
|
}
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"리비전 업로드 실패: {e}")
|
|
raise HTTPException(status_code=500, detail=f"리비전 업로드 실패: {str(e)}")
|
|
|
|
@router.get("/categories/{file_id}")
|
|
async def get_available_categories(
|
|
file_id: int,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""파일의 사용 가능한 카테고리 목록 조회"""
|
|
try:
|
|
# 파일에 포함된 카테고리별 자재 수 조회
|
|
categories_query = db.query(
|
|
Material.classified_category,
|
|
db.func.count(Material.id).label('count')
|
|
).filter(
|
|
Material.file_id == file_id
|
|
).group_by(Material.classified_category).all()
|
|
|
|
categories = []
|
|
for category, count in categories_query:
|
|
if category and count > 0:
|
|
categories.append({
|
|
'category': category,
|
|
'count': count
|
|
})
|
|
|
|
return {
|
|
"success": True,
|
|
"data": {
|
|
"file_id": file_id,
|
|
"categories": categories
|
|
}
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"카테고리 조회 실패: {e}")
|
|
raise HTTPException(status_code=500, detail=f"카테고리 조회 실패: {str(e)}")
|
|
|
|
@router.get("/changed-materials/{current_file_id}/{previous_file_id}")
|
|
async def get_changed_materials_only(
|
|
current_file_id: int,
|
|
previous_file_id: int,
|
|
categories: str = None, # 쉼표로 구분된 카테고리 목록
|
|
username: str = "system",
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""변경된 자재만 필터링하여 반환"""
|
|
try:
|
|
service = SimpleRevisionService(db)
|
|
|
|
# 카테고리 파싱
|
|
category_list = None
|
|
if categories:
|
|
category_list = [cat.strip().upper() for cat in categories.split(',')]
|
|
|
|
result = service.get_changed_materials_only(
|
|
current_file_id, previous_file_id, category_list, username
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"변경된 자재 조회 완료 ({result['total_changed_materials']}개 변경)",
|
|
"data": result
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"변경된 자재 조회 실패: {e}")
|
|
raise HTTPException(status_code=500, detail=f"변경된 자재 조회 실패: {str(e)}")
|
|
|
|
@router.get("/history/{job_no}")
|
|
async def get_revision_history(
|
|
job_no: str,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""작업 번호별 리비전 히스토리 조회"""
|
|
try:
|
|
from ..models import SimpleRevisionComparison
|
|
|
|
comparisons = db.query(SimpleRevisionComparison).filter(
|
|
SimpleRevisionComparison.job_no == job_no
|
|
).order_by(SimpleRevisionComparison.created_at.desc()).all()
|
|
|
|
history = []
|
|
for comp in comparisons:
|
|
history.append({
|
|
'comparison_id': comp.id,
|
|
'category': comp.category,
|
|
'created_at': comp.created_at.isoformat(),
|
|
'created_by': comp.created_by_username,
|
|
'summary': {
|
|
'added': comp.added_count,
|
|
'removed': comp.removed_count,
|
|
'changed': comp.changed_count,
|
|
'unchanged': comp.unchanged_count
|
|
}
|
|
})
|
|
|
|
return {
|
|
"success": True,
|
|
"data": {
|
|
"job_no": job_no,
|
|
"history": history
|
|
}
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"리비전 히스토리 조회 실패: {e}")
|
|
raise HTTPException(status_code=500, detail=f"리비전 히스토리 조회 실패: {str(e)}")
|