Files
TK-BOM-Project/backend/app/routers/pipe_excel.py
Hyungi Ahn 8f42a1054e
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
🔧 완전한 스키마 자동화 시스템 구축
 주요 기능:
- 완전한 데이터베이스 스키마 분석 및 자동 마이그레이션 시스템
- 44개 테이블 완전 지원 (운영 서버 43개 + 1개 추가)
- 누락된 테이블/컬럼 자동 감지 및 생성

🔧 해결된 스키마 문제:
- users.status 컬럼 누락 → 자동 추가
- files 테이블 4개 컬럼 누락 → 자동 추가
- materials 테이블 22개 컬럼 누락 → 자동 추가
- support_details, purchase_requests, purchase_request_items 테이블 누락 → 자동 생성
- material_purchase_tracking.description, purchase_status 컬럼 누락 → 자동 추가

🚀 자동화 도구:
- schema_analyzer.py: 코드와 DB 스키마 비교 분석
- auto_migrator.py: 자동 마이그레이션 실행
- docker_migrator.py: Docker 환경용 간편 마이그레이션
- schema_monitor.py: 실시간 스키마 모니터링

📋 리비전 관리 시스템:
- 8개 카테고리별 리비전 페이지 구현
- PIPE Cutting Plan 관리 시스템
- PIPE Issue Management 시스템
- 완전한 리비전 비교 및 추적 기능

🎯 사용법:
docker exec tk-mp-backend python3 scripts/docker_migrator.py

앞으로 스키마 문제가 발생하면 위 명령 하나로 자동 해결!
2025-10-21 10:34:45 +09:00

225 lines
7.5 KiB
Python

"""
PIPE 스냅샷 Excel 내보내기 API 라우터
확정된 Cutting Plan의 고정된 Excel 내보내기 기능
"""
import logging
from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, status, Response
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from io import BytesIO
from ..database import get_db
from ..services.pipe_snapshot_excel_service import get_pipe_snapshot_excel_service, PipeSnapshotExcelService
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/pipe-excel", tags=["pipe-excel"])
@router.get("/export-finalized/{job_no}")
async def export_finalized_cutting_plan(
job_no: str,
db: Session = Depends(get_db)
):
"""
확정된 Cutting Plan Excel 내보내기
- 스냅샷 데이터 기준으로 고정된 Excel 생성
- 리비전과 무관하게 동일한 데이터 제공
"""
try:
service = get_pipe_snapshot_excel_service(db)
result = service.export_finalized_cutting_plan(job_no)
if not result["success"]:
if "확정된 Cutting Plan이 없습니다" in result["message"]:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=result["message"]
)
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result["message"]
)
# Excel 파일 스트리밍 응답
excel_buffer = result["excel_buffer"]
filename = result["filename"]
# 파일 다운로드를 위한 헤더 설정
headers = {
'Content-Disposition': f'attachment; filename="{filename}"',
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
}
return StreamingResponse(
BytesIO(excel_buffer.getvalue()),
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers=headers
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to export finalized cutting plan: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"확정된 Excel 내보내기 실패: {str(e)}"
)
@router.get("/check-finalization/{job_no}")
async def check_finalization_status(
job_no: str,
db: Session = Depends(get_db)
):
"""
Cutting Plan 확정 상태 확인
- 확정된 Excel 내보내기 가능 여부 확인
"""
try:
service = get_pipe_snapshot_excel_service(db)
result = service.check_finalization_status(job_no)
return result
except Exception as e:
logger.error(f"Failed to check finalization status: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"확정 상태 확인 실패: {str(e)}"
)
@router.get("/preview-finalized/{job_no}")
async def preview_finalized_data(
job_no: str,
db: Session = Depends(get_db)
):
"""
확정된 데이터 미리보기
- Excel 생성 전 데이터 확인용
"""
try:
from ..services.pipe_issue_snapshot_service import get_pipe_issue_snapshot_service
# 스냅샷 상태 확인
snapshot_service = get_pipe_issue_snapshot_service(db)
snapshot_info = snapshot_service.get_snapshot_info(job_no)
if not snapshot_info["has_snapshot"] or not snapshot_info["is_locked"]:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="확정된 Cutting Plan이 없습니다."
)
# 스냅샷 데이터 조회
snapshot_id = snapshot_info["snapshot_id"]
segments = snapshot_service.get_snapshot_segments(snapshot_id)
# 구역별/도면별 통계
area_stats = {}
drawing_stats = {}
material_stats = {}
for segment in segments:
# 구역별 통계
area = segment.get("area", "미할당")
if area not in area_stats:
area_stats[area] = {"count": 0, "total_length": 0}
area_stats[area]["count"] += 1
area_stats[area]["total_length"] += segment.get("length_mm", 0)
# 도면별 통계
drawing = segment.get("drawing_name", "UNKNOWN")
if drawing not in drawing_stats:
drawing_stats[drawing] = {"count": 0, "total_length": 0}
drawing_stats[drawing]["count"] += 1
drawing_stats[drawing]["total_length"] += segment.get("length_mm", 0)
# 재질별 통계
material = segment.get("material_grade", "UNKNOWN")
if material not in material_stats:
material_stats[material] = {"count": 0, "total_length": 0}
material_stats[material]["count"] += 1
material_stats[material]["total_length"] += segment.get("length_mm", 0)
return {
"job_no": job_no,
"snapshot_info": snapshot_info,
"preview_data": {
"total_segments": len(segments),
"area_stats": area_stats,
"drawing_stats": drawing_stats,
"material_stats": material_stats
},
"sample_segments": segments[:10] if segments else [], # 처음 10개만 미리보기
"can_export": True,
"message": "확정된 데이터 미리보기가 준비되었습니다."
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to preview finalized data: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"데이터 미리보기 실패: {str(e)}"
)
@router.post("/export-temp/{job_no}")
async def export_temp_cutting_plan(
job_no: str,
db: Session = Depends(get_db)
):
"""
임시 Cutting Plan Excel 내보내기 (구현 예정)
- 현재 작업 중인 데이터 기준
- 리비전 시 변경될 수 있는 데이터
"""
try:
# TODO: 임시 Excel 내보내기 구현
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="임시 Excel 내보내기 기능은 구현 예정입니다."
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to export temp cutting plan: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"임시 Excel 내보내기 실패: {str(e)}"
)
@router.get("/download-history/{job_no}")
async def get_download_history(
job_no: str,
db: Session = Depends(get_db)
):
"""
Excel 다운로드 이력 조회 (구현 예정)
- 확정된 Excel 다운로드 기록
- 다운로드 시간 및 사용자 추적
"""
try:
# TODO: 다운로드 이력 추적 구현
return {
"job_no": job_no,
"download_history": [],
"message": "다운로드 이력 추적 기능은 구현 예정입니다."
}
except Exception as e:
logger.error(f"Failed to get download history: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"다운로드 이력 조회 실패: {str(e)}"
)