feat: BOM 관리 시스템 대폭 개선 및 Docker 배포 가이드 추가
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

- 🎨 UI/UX 개선: 데본씽크 스타일 모던 디자인 적용
- 📁 컴포넌트 구조 개선: 폴더별 체계적 관리 (common/, bom/, materials/)
- 🔧 BOM 관리 페이지 리팩토링: NewMaterialsPage → BOMManagementPage + 카테고리별 컴포넌트 분리
- 💾 구매신청 기능 개선: 선택된 자재 비활성화, 제목 편집, 엑셀 다운로드
- 📊 자재 표시 개선: 타입/서브타입 컬럼 정리, 상세 정보 복원
- 🐛 CSS 빌드 오류 수정: NewMaterialsPage.css 문법 오류 해결
- 📚 문서화: PAGES_GUIDE.md 추가, README에 Docker 캐시 문제 해결 가이드 추가
- 🔄 API 개선: 구매신청 자재 조회, 제목 수정 엔드포인트 추가
This commit is contained in:
hyungi
2025-10-16 12:45:23 +09:00
parent 5aef867110
commit 64fd9ad3d2
31 changed files with 7450 additions and 1604 deletions

View File

@@ -32,6 +32,13 @@ from app.services.revision_comparator import get_revision_comparison
router = APIRouter()
class ExcelSaveRequest(BaseModel):
file_id: int
category: str
materials: List[Dict]
filename: str
user_id: Optional[int] = None
def extract_enhanced_material_grade(description: str, original_grade: str, category: str) -> str:
"""
원본 설명에서 개선된 재질 정보를 추출
@@ -3039,9 +3046,9 @@ async def get_valve_details(
except Exception as e:
raise HTTPException(status_code=500, detail=f"VALVE 상세 정보 조회 실패: {str(e)}")
@router.get("/user-requirements")
@router.get("/{file_id}/user-requirements")
async def get_user_requirements(
file_id: Optional[int] = None,
file_id: int,
job_no: Optional[str] = None,
status: Optional[str] = None,
db: Session = Depends(get_db)
@@ -3729,4 +3736,174 @@ async def process_missing_drawings(
except Exception as e:
db.rollback()
raise HTTPException(status_code=500, detail=f"도면 처리 실패: {str(e)}")
raise HTTPException(status_code=500, detail=f"도면 처리 실패: {str(e)}")
@router.post("/save-excel")
async def save_excel_file(
request: ExcelSaveRequest,
current_user: dict = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""
엑셀 파일을 서버에 저장하고 메타데이터를 기록
"""
try:
# 엑셀 저장 디렉토리 생성
excel_dir = Path("uploads/excel_exports")
excel_dir.mkdir(parents=True, exist_ok=True)
# 파일 경로 생성
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
safe_filename = f"{request.category}_{timestamp}_{request.filename}"
file_path = excel_dir / safe_filename
# 엑셀 파일 생성 (openpyxl 사용)
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment
wb = openpyxl.Workbook()
ws = wb.active
ws.title = request.category
# 헤더 설정
headers = ['TAGNO', '품목명', '수량', '통화구분', '단가', '크기', '압력등급', '스케줄',
'재질', '상세내역', '사용자요구', '관리항목1', '관리항목2', '관리항목3',
'관리항목4', '납기일(YYYY-MM-DD)']
# 헤더 스타일
header_font = Font(bold=True, color="FFFFFF")
header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
header_alignment = Alignment(horizontal="center", vertical="center")
# 헤더 작성
for col, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_alignment
# 데이터 작성
for row_idx, material in enumerate(request.materials, 2):
# 기본 데이터
data = [
'', # TAGNO
request.category, # 품목명
material.get('quantity', 0), # 수량
'KRW', # 통화구분
1, # 단가
material.get('size_spec', '-'), # 크기
'-', # 압력등급
material.get('schedule', '-'), # 스케줄
material.get('full_material_grade', material.get('material_grade', '-')), # 재질
'-', # 상세내역
material.get('user_requirement', ''), # 사용자요구
'', '', '', '', # 관리항목들
datetime.now().strftime('%Y-%m-%d') # 납기일
]
# 데이터 입력
for col, value in enumerate(data, 1):
ws.cell(row=row_idx, column=col, value=value)
# 엑셀 파일 저장
wb.save(file_path)
# 데이터베이스에 메타데이터 저장 (테이블이 없으면 무시)
try:
save_query = text("""
INSERT INTO excel_exports (
file_id, category, filename, file_path,
material_count, created_by, created_at
) VALUES (
:file_id, :category, :filename, :file_path,
:material_count, :user_id, :created_at
)
""")
db.execute(save_query, {
"file_id": request.file_id,
"category": request.category,
"filename": safe_filename,
"file_path": str(file_path),
"material_count": len(request.materials),
"user_id": current_user.get('id'),
"created_at": datetime.now()
})
db.commit()
except Exception as db_error:
logger.warning(f"엑셀 메타데이터 저장 실패 (파일은 저장됨): {str(db_error)}")
# 메타데이터 저장 실패해도 파일은 저장되었으므로 계속 진행
logger.info(f"엑셀 파일 저장 완료: {safe_filename}")
return {
"success": True,
"message": "엑셀 파일이 성공적으로 저장되었습니다.",
"filename": safe_filename,
"file_path": str(file_path),
"material_count": len(request.materials)
}
except Exception as e:
logger.error(f"엑셀 파일 저장 실패: {str(e)}")
raise HTTPException(status_code=500, detail=f"엑셀 파일 저장 실패: {str(e)}")
@router.get("/excel-exports")
async def get_excel_exports(
file_id: Optional[int] = None,
category: Optional[str] = None,
current_user: dict = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""
저장된 엑셀 파일 목록 조회
"""
try:
query = text("""
SELECT
id, file_id, category, filename, file_path,
material_count, created_by, created_at
FROM excel_exports
WHERE 1=1
""")
params = {}
if file_id:
query = text(str(query) + " AND file_id = :file_id")
params["file_id"] = file_id
if category:
query = text(str(query) + " AND category = :category")
params["category"] = category
query = text(str(query) + " ORDER BY created_at DESC")
result = db.execute(query, params).fetchall()
exports = []
for row in result:
exports.append({
"id": row.id,
"file_id": row.file_id,
"category": row.category,
"filename": row.filename,
"file_path": row.file_path,
"material_count": row.material_count,
"created_by": row.created_by,
"created_at": row.created_at.isoformat() if row.created_at else None
})
return {
"success": True,
"exports": exports
}
except Exception as e:
logger.error(f"엑셀 내보내기 목록 조회 실패: {str(e)}")
return {
"success": False,
"exports": [],
"message": "엑셀 내보내기 목록을 조회할 수 없습니다."
}