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 # FastAPI 앱 생성 app = FastAPI( title="TK-MP BOM Management API", description="자재 분류 및 프로젝트 관리 시스템", version="1.0.0" ) # CORS 설정 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 라우터들 import 및 등록 try: from .routers import files app.include_router(files.router, prefix="/files", tags=["files"]) except ImportError: print("files 라우터를 찾을 수 없습니다") try: from .routers import jobs app.include_router(jobs.router, prefix="/jobs", tags=["jobs"]) except ImportError: print("jobs 라우터를 찾을 수 없습니다") try: from .routers import purchase app.include_router(purchase.router, tags=["purchase"]) except ImportError: print("purchase 라우터를 찾을 수 없습니다") # 파일 목록 조회 API @app.get("/files") async def get_files( job_no: Optional[str] = None, # project_id 대신 job_no 사용 show_history: bool = False, # 이력 표시 여부 db: Session = Depends(get_db) ): """파일 목록 조회 (BOM별 그룹화)""" try: if show_history: # 전체 이력 표시 query = "SELECT * FROM files" params = {} if job_no: query += " WHERE job_no = :job_no" params["job_no"] = job_no query += " ORDER BY original_filename, revision DESC" else: # 최신 리비전만 표시 if job_no: query = """ SELECT f1.* FROM files f1 INNER JOIN ( SELECT original_filename, MAX(revision) as max_revision FROM files WHERE job_no = :job_no GROUP BY original_filename ) f2 ON f1.original_filename = f2.original_filename AND f1.revision = f2.max_revision WHERE f1.job_no = :job_no ORDER BY f1.upload_date DESC """ params = {"job_no": job_no} else: # job_no가 없으면 전체 파일 조회 query = "SELECT * FROM files ORDER BY upload_date DESC" params = {} result = db.execute(text(query), params) files = result.fetchall() return [ { "id": f.id, "filename": f.original_filename, "original_filename": f.original_filename, "name": f.original_filename, "job_no": f.job_no, # job_no 사용 "bom_name": f.bom_name or f.original_filename, # 실제 bom_name 값 사용, 없으면 파일명 "bom_type": f.file_type or "unknown", # file_type을 BOM 종류로 사용 "status": "active" if f.is_active else "inactive", # is_active 상태 "file_size": f.file_size, "created_at": f.upload_date, "upload_date": f.upload_date, "revision": f.revision or "Rev.0", # 실제 리비전 또는 기본값 "description": f"파일: {f.original_filename}" } for f in files ] except Exception as e: print(f"파일 목록 조회 에러: {str(e)}") return {"error": f"파일 목록 조회 실패: {str(e)}"} # 파일 삭제 API @app.delete("/files/{file_id}") async def delete_file( file_id: int, db: Session = Depends(get_db) ): """파일 삭제""" try: # 먼저 파일 정보 조회 file_query = text("SELECT * FROM files WHERE id = :file_id") file_result = db.execute(file_query, {"file_id": file_id}) file = file_result.fetchone() if not file: return {"error": "파일을 찾을 수 없습니다"} # 먼저 상세 테이블의 데이터 삭제 (외래 키 제약 조건 때문) # 각 자재 타입별 상세 테이블 데이터 삭제 detail_tables = [ 'pipe_details', 'fitting_details', 'valve_details', 'flange_details', 'bolt_details', 'gasket_details', 'instrument_details' ] # 해당 파일의 materials ID 조회 material_ids_query = text("SELECT id FROM materials WHERE file_id = :file_id") material_ids_result = db.execute(material_ids_query, {"file_id": file_id}) material_ids = [row[0] for row in material_ids_result] if material_ids: # 각 상세 테이블에서 관련 데이터 삭제 for table in detail_tables: delete_detail_query = text(f"DELETE FROM {table} WHERE material_id = ANY(:material_ids)") db.execute(delete_detail_query, {"material_ids": material_ids}) # materials 테이블 데이터 삭제 materials_query = text("DELETE FROM materials WHERE file_id = :file_id") db.execute(materials_query, {"file_id": file_id}) # 파일 삭제 delete_query = text("DELETE FROM files WHERE id = :file_id") db.execute(delete_query, {"file_id": file_id}) db.commit() return {"success": True, "message": "파일과 관련 데이터가 삭제되었습니다"} except Exception as e: db.rollback() return {"error": f"파일 삭제 실패: {str(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)}"} # 파일 업로드는 /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)