- 리비전 관리 라우터 및 서비스 추가 (revision_management.py, revision_comparison_service.py, revision_session_service.py) - 구매확정 기능 구현: materials 테이블에 purchase_confirmed 필드 추가 및 업데이트 로직 - 리비전 비교 로직 구현: 구매확정된 자재 기반으로 신규/변경 자재 자동 분류 - 데이터베이스 스키마 확장: revision_sessions, revision_material_changes, inventory_transfers 테이블 추가 - 구매신청 생성 시 자재 상세 정보 저장 및 purchase_confirmed 자동 업데이트 - 프론트엔드: 리비전 관리 컴포넌트 및 hooks 추가 - 파일 목록 조회 API 추가 (/files/list) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
263 lines
8.8 KiB
Python
263 lines
8.8 KiB
Python
from fastapi import FastAPI, UploadFile, File, Form
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from sqlalchemy import text
|
|
from .database import get_db
|
|
from sqlalchemy.orm import Session
|
|
from fastapi import Depends
|
|
from typing import Optional, List, Dict
|
|
import os
|
|
import shutil
|
|
# 설정 및 로깅 import
|
|
from .config import get_settings
|
|
from .utils.logger import get_logger
|
|
from .utils.error_handlers import setup_error_handlers
|
|
|
|
# 설정 로드
|
|
settings = get_settings()
|
|
|
|
# 로거 설정
|
|
logger = get_logger(__name__)
|
|
|
|
# FastAPI 앱 생성 (요청 크기 제한 증가)
|
|
app = FastAPI(
|
|
title=settings.app_name,
|
|
description="자재 분류 및 프로젝트 관리 시스템",
|
|
version=settings.app_version,
|
|
debug=settings.debug
|
|
)
|
|
|
|
# 요청 크기 제한 설정 (100MB로 증가)
|
|
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
from starlette.requests import Request
|
|
from starlette.responses import Response
|
|
|
|
class RequestSizeLimitMiddleware(BaseHTTPMiddleware):
|
|
def __init__(self, app, max_request_size: int = 100 * 1024 * 1024): # 100MB
|
|
super().__init__(app)
|
|
self.max_request_size = max_request_size
|
|
|
|
async def dispatch(self, request: Request, call_next):
|
|
if "content-length" in request.headers:
|
|
content_length = int(request.headers["content-length"])
|
|
if content_length > self.max_request_size:
|
|
return Response("Request Entity Too Large", status_code=413)
|
|
return await call_next(request)
|
|
|
|
# 요청 크기 제한 미들웨어 추가
|
|
app.add_middleware(RequestSizeLimitMiddleware, max_request_size=100 * 1024 * 1024)
|
|
|
|
# 에러 핸들러 설정
|
|
setup_error_handlers(app)
|
|
|
|
# CORS 설정 (환경별 분리)
|
|
cors_config = settings.get_cors_config()
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
**cors_config
|
|
)
|
|
|
|
logger.info(f"CORS origins configured for {settings.environment}: {settings.security.cors_origins}")
|
|
|
|
# 라우터들 import 및 등록 - files 라우터를 최우선으로 등록
|
|
try:
|
|
from .routers import files
|
|
app.include_router(files.router, prefix="/files", tags=["files"])
|
|
logger.info("FILES 라우터 등록 완료 - 최우선")
|
|
except ImportError:
|
|
logger.warning("files 라우터를 찾을 수 없습니다")
|
|
|
|
try:
|
|
from .routers import jobs
|
|
app.include_router(jobs.router, prefix="/jobs", tags=["jobs"])
|
|
except ImportError:
|
|
logger.warning("jobs 라우터를 찾을 수 없습니다")
|
|
|
|
try:
|
|
from .routers import purchase
|
|
app.include_router(purchase.router, tags=["purchase"])
|
|
except ImportError:
|
|
logger.warning("purchase 라우터를 찾을 수 없습니다")
|
|
|
|
try:
|
|
from .routers import material_comparison
|
|
app.include_router(material_comparison.router, tags=["material-comparison"])
|
|
except ImportError:
|
|
logger.warning("material_comparison 라우터를 찾을 수 없습니다")
|
|
|
|
try:
|
|
from .routers import dashboard
|
|
app.include_router(dashboard.router, tags=["dashboard"])
|
|
except ImportError:
|
|
logger.warning("dashboard 라우터를 찾을 수 없습니다")
|
|
|
|
# 리비전 관리 라우터 (임시 비활성화)
|
|
# try:
|
|
# from .routers import revision_management
|
|
# app.include_router(revision_management.router, tags=["revision-management"])
|
|
# except ImportError:
|
|
# logger.warning("revision_management 라우터를 찾을 수 없습니다")
|
|
|
|
try:
|
|
from .routers import tubing
|
|
app.include_router(tubing.router, prefix="/tubing", tags=["tubing"])
|
|
except ImportError:
|
|
logger.warning("tubing 라우터를 찾을 수 없습니다")
|
|
|
|
# 구매 추적 라우터
|
|
try:
|
|
from .routers import purchase_tracking
|
|
app.include_router(purchase_tracking.router)
|
|
except ImportError:
|
|
logger.warning("purchase_tracking 라우터를 찾을 수 없습니다")
|
|
|
|
# 엑셀 내보내기 관리 라우터
|
|
try:
|
|
from .routers import export_manager
|
|
app.include_router(export_manager.router)
|
|
except ImportError:
|
|
logger.warning("export_manager 라우터를 찾을 수 없습니다")
|
|
|
|
# 구매신청 관리 라우터
|
|
try:
|
|
from .routers import purchase_request
|
|
app.include_router(purchase_request.router)
|
|
logger.info("purchase_request 라우터 등록 완료")
|
|
except ImportError as e:
|
|
logger.warning(f"purchase_request 라우터를 찾을 수 없습니다: {e}")
|
|
|
|
# 자재 관리 라우터
|
|
try:
|
|
from .routers import materials
|
|
app.include_router(materials.router)
|
|
logger.info("materials 라우터 등록 완료")
|
|
except ImportError as e:
|
|
logger.warning(f"materials 라우터를 찾을 수 없습니다: {e}")
|
|
|
|
# 파일 관리 API 라우터 등록 (비활성화 - files 라우터와 충돌 방지)
|
|
# try:
|
|
# from .api import file_management
|
|
# app.include_router(file_management.router, tags=["file-management"])
|
|
# logger.info("파일 관리 API 라우터 등록 완료")
|
|
# except ImportError as e:
|
|
# logger.warning(f"파일 관리 라우터를 찾을 수 없습니다: {e}")
|
|
logger.info("파일 관리 API 라우터 비활성화됨 (files 라우터 사용)")
|
|
|
|
# 인증 API 라우터 등록
|
|
try:
|
|
from .auth import auth_router, setup_router
|
|
from .auth.signup_routes import router as signup_router
|
|
app.include_router(auth_router, prefix="/auth", tags=["authentication"])
|
|
app.include_router(setup_router, prefix="/setup", tags=["system-setup"])
|
|
app.include_router(signup_router, tags=["signup"])
|
|
logger.info("인증 API 라우터 등록 완료")
|
|
logger.info("시스템 설정 API 라우터 등록 완료")
|
|
logger.info("회원가입 API 라우터 등록 완료")
|
|
except ImportError as e:
|
|
logger.warning(f"인증 라우터를 찾을 수 없습니다: {e}")
|
|
|
|
# 프로젝트 관리 API (비활성화 - jobs 테이블 사용)
|
|
# projects 테이블은 더 이상 사용하지 않음
|
|
# ):
|
|
# """프로젝트 수정"""
|
|
# try:
|
|
# update_query = text("""
|
|
# UPDATE projects
|
|
# SET project_name = :project_name, status = :status
|
|
# WHERE id = :project_id
|
|
# """)
|
|
#
|
|
# db.execute(update_query, {
|
|
# "project_id": project_id,
|
|
# "project_name": project_data["project_name"],
|
|
# "status": project_data["status"]
|
|
# })
|
|
#
|
|
# db.commit()
|
|
# return {"success": True}
|
|
# except Exception as e:
|
|
# db.rollback()
|
|
# return {"error": f"프로젝트 수정 실패: {str(e)}"}
|
|
|
|
# @app.delete("/projects/{project_id}")
|
|
# async def delete_project(
|
|
# project_id: int,
|
|
# db: Session = Depends(get_db)
|
|
# ):
|
|
# """프로젝트 삭제"""
|
|
# try:
|
|
# delete_query = text("DELETE FROM projects WHERE id = :project_id")
|
|
# db.execute(delete_query, {"project_id": project_id})
|
|
# db.commit()
|
|
# return {"success": True}
|
|
# except Exception as e:
|
|
# db.rollback()
|
|
# return {"error": f"프로젝트 삭제 실패: {str(e)}"}
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
return {
|
|
"message": "TK-MP BOM Management API",
|
|
"version": "1.0.0",
|
|
"endpoints": ["/docs", "/jobs", "/files", "/projects"]
|
|
}
|
|
|
|
# Jobs API
|
|
# @app.get("/jobs")
|
|
# async def get_jobs(db: Session = Depends(get_db)):
|
|
# """Jobs 목록 조회"""
|
|
# try:
|
|
# # jobs 테이블에서 데이터 조회
|
|
# query = text("""
|
|
# SELECT
|
|
# job_no,
|
|
# job_name,
|
|
# client_name,
|
|
# end_user,
|
|
# epc_company,
|
|
# status,
|
|
# created_at
|
|
# FROM jobs
|
|
# WHERE is_active = true
|
|
# ORDER BY created_at DESC
|
|
# """)
|
|
#
|
|
# result = db.execute(query)
|
|
# jobs = result.fetchall()
|
|
#
|
|
# return [
|
|
# {
|
|
# "job_no": job.job_no,
|
|
# "job_name": job.job_name,
|
|
# "client_name": job.client_name,
|
|
# "end_user": job.end_user,
|
|
# "epc_company": job.epc_company,
|
|
# "status": job.status or "진행중",
|
|
# "created_at": job.created_at
|
|
# }
|
|
# for job in jobs
|
|
# ]
|
|
# except Exception as e:
|
|
# print(f"Jobs 조회 에러: {str(e)}")
|
|
# return {"error": f"Jobs 조회 실패: {str(e)}"}
|
|
|
|
# 리비전 관리 라우터
|
|
try:
|
|
from .routers import revision_management
|
|
app.include_router(revision_management.router)
|
|
logger.info("revision_management 라우터 등록 완료")
|
|
except ImportError as e:
|
|
logger.warning(f"revision_management 라우터를 찾을 수 없습니다: {e}")
|
|
|
|
# 파일 업로드는 /files/upload 엔드포인트를 사용하세요 (routers/files.py)
|
|
|
|
# parse_file과 classify_material_item 함수는 routers/files.py로 이동되었습니다
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
return {"status": "healthy", "timestamp": "2024-07-15"}
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=8000, reload=True)
|