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 앞으로 스키마 문제가 발생하면 위 명령 하나로 자동 해결!
225 lines
7.5 KiB
Python
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)}"
|
|
)
|