🔧 완전한 스키마 자동화 시스템 구축
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
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 앞으로 스키마 문제가 발생하면 위 명령 하나로 자동 해결!
This commit is contained in:
258
backend/app/routers/revision_status.py
Normal file
258
backend/app/routers/revision_status.py
Normal file
@@ -0,0 +1,258 @@
|
||||
"""
|
||||
리비전 상태 관리 API 엔드포인트
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..database import get_db
|
||||
from ..auth.middleware import get_current_user
|
||||
from ..services.revision_status_service import RevisionStatusService
|
||||
from ..auth.models import User
|
||||
from ..utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/revision-status", tags=["Revision Status"])
|
||||
|
||||
|
||||
class CreateComparisonRequest(BaseModel):
|
||||
job_no: str
|
||||
current_file_id: int
|
||||
previous_file_id: int
|
||||
comparison_result: dict
|
||||
|
||||
|
||||
class RejectComparisonRequest(BaseModel):
|
||||
reason: str = ""
|
||||
|
||||
|
||||
@router.get("/{job_no}/{file_id}")
|
||||
async def get_revision_status(
|
||||
job_no: str,
|
||||
file_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
리비전 상태 조회
|
||||
"""
|
||||
|
||||
try:
|
||||
status_service = RevisionStatusService(db)
|
||||
|
||||
revision_status = status_service.get_revision_status(job_no, file_id)
|
||||
|
||||
if "error" in revision_status:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=revision_status["error"]
|
||||
)
|
||||
|
||||
logger.info(f"Retrieved revision status for {job_no}/{file_id}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": revision_status,
|
||||
"message": "리비전 상태 조회 완료"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get revision status: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"리비전 상태 조회 중 오류가 발생했습니다: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/history/{job_no}")
|
||||
async def get_revision_history(
|
||||
job_no: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
작업의 전체 리비전 히스토리 조회
|
||||
"""
|
||||
|
||||
try:
|
||||
status_service = RevisionStatusService(db)
|
||||
|
||||
history = status_service.get_revision_history(job_no)
|
||||
|
||||
logger.info(f"Retrieved revision history for {job_no}: {len(history)} revisions")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": history,
|
||||
"message": "리비전 히스토리 조회 완료"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get revision history: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"리비전 히스토리 조회 중 오류가 발생했습니다: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/create-comparison")
|
||||
async def create_comparison_record(
|
||||
request: CreateComparisonRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
리비전 비교 기록 생성
|
||||
"""
|
||||
|
||||
try:
|
||||
status_service = RevisionStatusService(db)
|
||||
|
||||
comparison_id = status_service.create_revision_comparison_record(
|
||||
job_no=request.job_no,
|
||||
current_file_id=request.current_file_id,
|
||||
previous_file_id=request.previous_file_id,
|
||||
comparison_result=request.comparison_result,
|
||||
created_by=current_user.username
|
||||
)
|
||||
|
||||
logger.info(f"Created revision comparison record: {comparison_id}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": {"comparison_id": comparison_id},
|
||||
"message": "리비전 비교 기록 생성 완료"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create comparison record: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"비교 기록 생성 중 오류가 발생했습니다: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/apply-comparison/{comparison_id}")
|
||||
async def apply_comparison(
|
||||
comparison_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
리비전 비교 결과 적용
|
||||
"""
|
||||
|
||||
try:
|
||||
status_service = RevisionStatusService(db)
|
||||
|
||||
apply_result = status_service.apply_revision_comparison(
|
||||
comparison_id=comparison_id,
|
||||
applied_by=current_user.username
|
||||
)
|
||||
|
||||
if not apply_result["success"]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=apply_result["error"]
|
||||
)
|
||||
|
||||
logger.info(f"Applied revision comparison: {comparison_id}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": apply_result,
|
||||
"message": "리비전 비교 결과 적용 완료"
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to apply comparison {comparison_id}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"비교 결과 적용 중 오류가 발생했습니다: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/pending")
|
||||
async def get_pending_revisions(
|
||||
job_no: Optional[str] = Query(None, description="특정 작업의 대기 중인 리비전만 조회"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
대기 중인 리비전 목록 조회
|
||||
"""
|
||||
|
||||
try:
|
||||
status_service = RevisionStatusService(db)
|
||||
|
||||
pending_revisions = status_service.get_pending_revisions(job_no)
|
||||
|
||||
logger.info(f"Retrieved {len(pending_revisions)} pending revisions")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": pending_revisions,
|
||||
"message": "대기 중인 리비전 조회 완료"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get pending revisions: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"대기 중인 리비전 조회 중 오류가 발생했습니다: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/reject-comparison/{comparison_id}")
|
||||
async def reject_comparison(
|
||||
comparison_id: int,
|
||||
request: RejectComparisonRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
리비전 비교 결과 거부
|
||||
"""
|
||||
|
||||
try:
|
||||
# 비교 기록을 거부 상태로 업데이트
|
||||
update_query = """
|
||||
UPDATE revision_comparisons
|
||||
SET is_applied = false,
|
||||
notes = CONCAT(COALESCE(notes, ''), '\n거부됨: ', :reason, ' (by ', :rejected_by, ')')
|
||||
WHERE id = :comparison_id
|
||||
"""
|
||||
|
||||
from ..services.database_service import DatabaseService
|
||||
db_service = DatabaseService(db)
|
||||
|
||||
db_service.execute_query(update_query, {
|
||||
"comparison_id": comparison_id,
|
||||
"reason": request.reason or "사유 없음",
|
||||
"rejected_by": current_user.username
|
||||
})
|
||||
|
||||
db.commit()
|
||||
|
||||
logger.info(f"Rejected revision comparison: {comparison_id}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": {"comparison_id": comparison_id, "reason": request.reason},
|
||||
"message": "리비전 비교 결과 거부 완료"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to reject comparison {comparison_id}: {e}")
|
||||
db.rollback()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"비교 결과 거부 중 오류가 발생했습니다: {str(e)}"
|
||||
)
|
||||
Reference in New Issue
Block a user