Files
TK-BOM-Project/backend/app/routers/revision_material.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

200 lines
6.4 KiB
Python

"""
리비전 자재 처리 API 엔드포인트
"""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List, Dict, Any
from pydantic import BaseModel
from ..database import get_db
from ..auth.middleware import get_current_user
from ..services.revision_material_service import RevisionMaterialService
from ..auth.models import User
from ..utils.logger import get_logger
logger = get_logger(__name__)
router = APIRouter(prefix="/revision-material", tags=["Revision Material"])
class ProcessingResultRequest(BaseModel):
processing_results: List[Dict[str, Any]]
class MaterialProcessRequest(BaseModel):
action: str
additional_data: Dict[str, Any] = {}
@router.get("/category/{file_id}/{category}")
async def get_category_materials(
file_id: int,
category: str,
include_processing_info: bool = True,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
리비전 페이지용 카테고리별 자재 조회
"""
if category == 'PIPE':
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="PIPE 카테고리는 별도 처리가 필요합니다."
)
try:
material_service = RevisionMaterialService(db)
materials = material_service.get_category_materials_for_revision(
file_id=file_id,
category=category,
include_processing_info=include_processing_info
)
# 처리 정보 요약
processing_info = {
"total_materials": len(materials),
"by_status": {},
"by_priority": {"high": 0, "medium": 0, "low": 0}
}
for material in materials:
proc_info = material.get('processing_info', {})
status = proc_info.get('display_status', 'UNKNOWN')
priority = proc_info.get('priority', 'medium')
processing_info["by_status"][status] = processing_info["by_status"].get(status, 0) + 1
processing_info["by_priority"][priority] += 1
logger.info(f"Retrieved {len(materials)} materials for category {category} in file {file_id}")
return {
"success": True,
"data": {
"materials": materials,
"processing_info": processing_info
},
"message": f"{category} 카테고리 자재 조회 완료"
}
except Exception as e:
logger.error(f"Failed to get category materials: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"카테고리 자재 조회 중 오류가 발생했습니다: {str(e)}"
)
@router.post("/process/{material_id}")
async def process_material(
material_id: int,
request: MaterialProcessRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
개별 자재 처리
"""
try:
material_service = RevisionMaterialService(db)
# 자재 정보 조회
material_query = """
SELECT * FROM materials WHERE id = :material_id
"""
result = material_service.db_service.execute_query(
material_query, {"material_id": material_id}
)
material = result.fetchone()
if not material:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="자재를 찾을 수 없습니다."
)
material_dict = dict(material._mapping)
# 액션에 따른 처리
if request.action == "new_material":
processing_result = material_service.process_new_material(
material_dict,
material_dict.get('classified_category', 'UNKNOWN')
)
elif request.action == "remove_material":
processing_result = material_service.process_removed_material(
material_dict,
material_dict.get('classified_category', 'UNKNOWN')
)
else:
# 기본 처리 (구매 상태별)
# 이전 자재 정보가 필요한 경우 additional_data에서 가져옴
prev_material = request.additional_data.get('previous_material', material_dict)
processing_result = material_service.process_material_by_purchase_status(
prev_material,
material_dict,
material_dict.get('classified_category', 'UNKNOWN')
)
# 처리 결과 적용
apply_result = material_service.apply_material_processing_results([processing_result])
logger.info(f"Processed material {material_id} with action {request.action}")
return {
"success": True,
"data": {
"processing_result": processing_result,
"apply_result": apply_result
},
"message": "자재 처리 완료"
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to process material {material_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"자재 처리 중 오류가 발생했습니다: {str(e)}"
)
@router.post("/apply-results")
async def apply_processing_results(
request: ProcessingResultRequest,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
자재 처리 결과를 일괄 적용
"""
try:
material_service = RevisionMaterialService(db)
apply_result = material_service.apply_material_processing_results(
request.processing_results
)
logger.info(f"Applied {len(request.processing_results)} processing results")
return {
"success": True,
"data": apply_result,
"message": "처리 결과 적용 완료"
}
except Exception as e:
logger.error(f"Failed to apply processing results: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"처리 결과 적용 중 오류가 발생했습니다: {str(e)}"
)