""" 강화된 리비전 관리 API 엔드포인트 """ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List, Dict, Any, Optional from datetime import datetime from ..database import get_db from ..auth.middleware import get_current_user from ..services.enhanced_revision_service import EnhancedRevisionService from ..auth.models import User from ..models import RevisionComparison, RevisionChangeLog from ..utils.logger import get_logger logger = get_logger(__name__) router = APIRouter(prefix="/enhanced-revision", tags=["Enhanced Revision Management"]) @router.post("/compare-revisions") async def compare_revisions_enhanced( job_no: str, current_file_id: int, previous_file_id: Optional[int] = None, save_comparison: bool = True, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 강화된 리비전 비교 수행 Args: job_no: 작업 번호 current_file_id: 현재 파일 ID previous_file_id: 이전 파일 ID (None이면 자동 탐지) save_comparison: 비교 결과 저장 여부 """ try: revision_service = EnhancedRevisionService(db) # 리비전 비교 수행 comparison_result = revision_service.compare_revisions_with_purchase_status( job_no=job_no, current_file_id=current_file_id, previous_file_id=previous_file_id ) # 비교 결과 저장 (옵션) if save_comparison: comparison_record = RevisionComparison( job_no=job_no, current_file_id=current_file_id, previous_file_id=previous_file_id, comparison_result=comparison_result, summary_stats=comparison_result.get("summary", {}), created_by=current_user.username, is_applied=False ) db.add(comparison_record) db.commit() db.refresh(comparison_record) comparison_result["comparison_id"] = comparison_record.id logger.info(f"Enhanced revision comparison completed for job {job_no}") return { "success": True, "data": comparison_result, "message": "강화된 리비전 비교가 완료되었습니다." } except Exception as e: logger.error(f"Enhanced revision comparison failed: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"리비전 비교 중 오류가 발생했습니다: {str(e)}" ) @router.post("/apply-revision-changes/{comparison_id}") async def apply_revision_changes( comparison_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 리비전 변경사항을 실제 DB에 적용 Args: comparison_id: 비교 결과 ID """ try: # 비교 결과 조회 comparison = db.query(RevisionComparison).filter( RevisionComparison.id == comparison_id ).first() if not comparison: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="비교 결과를 찾을 수 없습니다." ) if comparison.is_applied: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="이미 적용된 비교 결과입니다." ) revision_service = EnhancedRevisionService(db) # 변경사항 적용 apply_result = revision_service.apply_revision_changes( comparison_result=comparison.comparison_result, current_file_id=comparison.current_file_id ) if apply_result["success"]: # 적용 완료 표시 comparison.is_applied = True comparison.applied_at = datetime.utcnow() comparison.applied_by = current_user.username db.commit() logger.info(f"Revision changes applied for comparison {comparison_id}") return { "success": True, "data": apply_result, "message": "리비전 변경사항이 성공적으로 적용되었습니다." } else: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=apply_result.get("message", "변경사항 적용 중 오류가 발생했습니다.") ) except HTTPException: raise except Exception as e: logger.error(f"Failed to apply revision changes: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"변경사항 적용 중 오류가 발생했습니다: {str(e)}" ) @router.get("/comparison-history/{job_no}") async def get_comparison_history( job_no: str, limit: int = 10, offset: int = 0, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 작업별 리비전 비교 이력 조회 Args: job_no: 작업 번호 limit: 조회 개수 제한 offset: 조회 시작 위치 """ try: comparisons = db.query(RevisionComparison).filter( RevisionComparison.job_no == job_no ).order_by( RevisionComparison.comparison_date.desc() ).offset(offset).limit(limit).all() result = [] for comp in comparisons: result.append({ "id": comp.id, "job_no": comp.job_no, "current_file_id": comp.current_file_id, "previous_file_id": comp.previous_file_id, "comparison_date": comp.comparison_date.isoformat(), "summary_stats": comp.summary_stats, "created_by": comp.created_by, "is_applied": comp.is_applied, "applied_at": comp.applied_at.isoformat() if comp.applied_at else None, "applied_by": comp.applied_by }) return { "success": True, "data": result, "total": len(result), "message": "리비전 비교 이력을 조회했습니다." } except Exception as e: logger.error(f"Failed to get comparison history: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"비교 이력 조회 중 오류가 발생했습니다: {str(e)}" ) @router.get("/comparison-details/{comparison_id}") async def get_comparison_details( comparison_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ 특정 비교 결과의 상세 정보 조회 Args: comparison_id: 비교 결과 ID """ try: comparison = db.query(RevisionComparison).filter( RevisionComparison.id == comparison_id ).first() if not comparison: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="비교 결과를 찾을 수 없습니다." ) # 변경 로그도 함께 조회 change_logs = db.query(RevisionChangeLog).filter( RevisionChangeLog.comparison_id == comparison_id ).all() result = { "comparison": { "id": comparison.id, "job_no": comparison.job_no, "current_file_id": comparison.current_file_id, "previous_file_id": comparison.previous_file_id, "comparison_date": comparison.comparison_date.isoformat(), "comparison_result": comparison.comparison_result, "summary_stats": comparison.summary_stats, "created_by": comparison.created_by, "is_applied": comparison.is_applied, "applied_at": comparison.applied_at.isoformat() if comparison.applied_at else None, "applied_by": comparison.applied_by }, "change_logs": [ { "id": log.id, "material_id": log.material_id, "change_type": log.change_type, "previous_data": log.previous_data, "current_data": log.current_data, "action_taken": log.action_taken, "notes": log.notes, "created_at": log.created_at.isoformat() } for log in change_logs ] } return { "success": True, "data": result, "message": "비교 결과 상세 정보를 조회했습니다." } except HTTPException: raise except Exception as e: logger.error(f"Failed to get comparison details: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"비교 결과 조회 중 오류가 발생했습니다: {str(e)}" ) @router.get("/pipe-length-summary/{file_id}") async def get_pipe_length_summary( file_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ PIPE 자재 길이 요약 정보 조회 Args: file_id: 파일 ID """ try: revision_service = EnhancedRevisionService(db) # PIPE 자재만 조회하여 도면-라인넘버별 길이 집계 pipe_materials = revision_service._get_materials_with_purchase_status(file_id) pipe_summary = {} for key, material in pipe_materials.items(): if material.get('classified_category') == 'PIPE': drawing_line = f"{material.get('drawing_name', 'Unknown')} - {material.get('line_no', 'Unknown')}" if drawing_line not in pipe_summary: pipe_summary[drawing_line] = { "drawing_name": material.get('drawing_name'), "line_no": material.get('line_no'), "material_grade": material.get('material_grade'), "schedule": material.get('schedule'), "nominal_size": material.get('main_nom'), "total_length": 0, "segment_count": 0, "purchase_status": "mixed" } pipe_summary[drawing_line]["total_length"] += material.get('total_length', 0) pipe_summary[drawing_line]["segment_count"] += 1 # 구매 상태 확인 if material.get('purchase_confirmed'): if pipe_summary[drawing_line]["purchase_status"] == "mixed": pipe_summary[drawing_line]["purchase_status"] = "purchased" else: if pipe_summary[drawing_line]["purchase_status"] == "purchased": pipe_summary[drawing_line]["purchase_status"] = "mixed" elif pipe_summary[drawing_line]["purchase_status"] != "mixed": pipe_summary[drawing_line]["purchase_status"] = "pending" return { "success": True, "data": { "file_id": file_id, "pipe_lines": list(pipe_summary.values()), "total_lines": len(pipe_summary), "total_length": sum(line["total_length"] for line in pipe_summary.values()) }, "message": "PIPE 자재 길이 요약을 조회했습니다." } except Exception as e: logger.error(f"Failed to get pipe length summary: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"PIPE 길이 요약 조회 중 오류가 발생했습니다: {str(e)}" ) @router.post("/recalculate-pipe-lengths/{file_id}") async def recalculate_pipe_lengths( file_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """ PIPE 자재 길이 재계산 및 업데이트 Args: file_id: 파일 ID """ try: revision_service = EnhancedRevisionService(db) # PIPE 자재 조회 및 길이 재계산 pipe_materials = revision_service._get_materials_with_purchase_status(file_id) updated_count = 0 for key, material in pipe_materials.items(): if material.get('classified_category') == 'PIPE': # total_length 업데이트 total_length = material.get('total_length', 0) update_query = """ UPDATE materials SET total_length = :total_length, updated_at = CURRENT_TIMESTAMP WHERE id = :material_id """ revision_service.db_service.execute_query(update_query, { "total_length": total_length, "material_id": material["id"] }) updated_count += 1 db.commit() logger.info(f"Recalculated pipe lengths for {updated_count} materials in file {file_id}") return { "success": True, "data": { "file_id": file_id, "updated_count": updated_count }, "message": f"PIPE 자재 {updated_count}개의 길이를 재계산했습니다." } except Exception as e: logger.error(f"Failed to recalculate pipe lengths: {e}") db.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"PIPE 길이 재계산 중 오류가 발생했습니다: {str(e)}" )