@router.post("/upload") async def upload_file( file: UploadFile = File(...), job_no: str = Form(...), revision: str = Form("Rev.0"), db: Session = Depends(get_db) ): # 1. Job 검증 (새로 추가!) job_validation = await validate_job_exists(job_no, db) if not job_validation["valid"]: raise HTTPException( status_code=400, detail=f"Job 오류: {job_validation['error']}" ) job_info = job_validation["job"] print(f"✅ Job 검증 완료: {job_info['job_no']} - {job_info['job_name']}") # 2. 파일 검증 if not validate_file_extension(file.filename): raise HTTPException( status_code=400, detail=f"지원하지 않는 파일 형식입니다. 허용된 확장자: {', '.join(ALLOWED_EXTENSIONS)}" ) if file.size and file.size > 10 * 1024 * 1024: raise HTTPException(status_code=400, detail="파일 크기는 10MB를 초과할 수 없습니다") # 3. 파일 저장 unique_filename = generate_unique_filename(file.filename) file_path = UPLOAD_DIR / unique_filename try: with open(file_path, "wb") as buffer: shutil.copyfileobj(file.file, buffer) except Exception as e: raise HTTPException(status_code=500, detail=f"파일 저장 실패: {str(e)}") # 4. 파일 파싱 및 자재 추출 try: materials_data = parse_file_data(str(file_path)) parsed_count = len(materials_data) # 파일 정보 저장 (job_no 사용!) file_insert_query = text(""" INSERT INTO files (filename, original_filename, file_path, job_no, revision, description, file_size, parsed_count, is_active) VALUES (:filename, :original_filename, :file_path, :job_no, :revision, :description, :file_size, :parsed_count, :is_active) RETURNING id """) file_result = db.execute(file_insert_query, { "filename": unique_filename, "original_filename": file.filename, "file_path": str(file_path), "job_no": job_no, # job_no 사용! "revision": revision, "description": f"BOM 파일 - {parsed_count}개 자재 ({job_info['job_name']})", "file_size": file.size, "parsed_count": parsed_count, "is_active": True }) file_id = file_result.fetchone()[0] # 자재 데이터 저장 materials_inserted = 0 for material_data in materials_data: material_insert_query = text(""" INSERT INTO materials ( file_id, original_description, quantity, unit, size_spec, material_grade, line_number, row_number, classified_category, classification_confidence, is_verified, created_at ) VALUES ( :file_id, :original_description, :quantity, :unit, :size_spec, :material_grade, :line_number, :row_number, :classified_category, :classification_confidence, :is_verified, :created_at ) """) db.execute(material_insert_query, { "file_id": file_id, "original_description": material_data["original_description"], "quantity": material_data["quantity"], "unit": material_data["unit"], "size_spec": material_data["size_spec"], "material_grade": material_data["material_grade"], "line_number": material_data["line_number"], "row_number": material_data["row_number"], "classified_category": None, "classification_confidence": None, "is_verified": False, "created_at": datetime.now() }) materials_inserted += 1 db.commit() return { "success": True, "message": f"Job '{job_info['job_name']}'에 BOM 파일 업로드 완료!", "job": { "job_no": job_info["job_no"], "job_name": job_info["job_name"], "status": job_info["status"] }, "file": { "id": file_id, "original_filename": file.filename, "parsed_count": parsed_count, "saved_count": materials_inserted }, "sample_materials": materials_data[:3] if materials_data else [] } except Exception as e: db.rollback() if os.path.exists(file_path): os.remove(file_path) raise HTTPException(status_code=500, detail=f"파일 처리 실패: {str(e)}")