feat: 모든 카테고리에 추가요청사항 저장 기능 및 엑셀 내보내기 개선
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

- 모든 BOM 카테고리(Pipe, Fitting, Flange, Gasket, Bolt, Support)에 추가요청사항 저장/편집 기능 추가
- 저장된 데이터의 카테고리 변경 및 페이지 새로고침 시 지속성 보장
- 백엔드 materials 테이블에 brand, user_requirement 컬럼 추가
- 새로운 /materials/{id}/brand, /materials/{id}/user-requirement PATCH API 엔드포인트 추가
- 모든 카테고리에서 Additional Request 컬럼 너비 확장 (UI 겹침 방지)
- GASKET 카테고리 엑셀 내보내기에 누락된 '추가요청사항' 컬럼 추가
- 엑셀 내보내기 시 저장된 추가요청사항이 우선 반영되도록 개선
- P열 납기일 규칙 유지하며 관리항목 개수 조정
This commit is contained in:
hyungi
2025-10-17 12:54:17 +09:00
parent 6b6360ecd5
commit f336b5a4a8
12 changed files with 1667 additions and 333 deletions

View File

@@ -119,6 +119,14 @@ try:
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

View File

@@ -1867,6 +1867,7 @@ async def get_materials(
m.created_at, m.classified_category, m.classification_confidence,
m.classification_details,
m.is_verified, m.verified_by, m.verified_at,
m.brand, m.user_requirement,
f.original_filename, f.project_id, f.job_no, f.revision,
p.official_project_code, p.project_name,
pd.outer_diameter, pd.schedule, pd.material_spec, pd.manufacturing_method,
@@ -2087,7 +2088,10 @@ async def get_materials(
"purchase_status": m.purchase_status,
"purchase_confirmed_by": m.confirmed_by,
"purchase_confirmed_at": m.confirmed_at,
"created_at": m.created_at
"created_at": m.created_at,
# 브랜드와 사용자 요구사항 필드 추가
"brand": m.brand,
"user_requirement": m.user_requirement
}
# 카테고리별 상세 정보 추가 (JOIN 결과 사용)

View File

@@ -0,0 +1,161 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from sqlalchemy import text
from pydantic import BaseModel
from ..database import get_db
from ..auth.middleware import get_current_user
router = APIRouter(prefix="/materials", tags=["materials"])
class BrandUpdate(BaseModel):
brand: str
class UserRequirementUpdate(BaseModel):
user_requirement: str
@router.patch("/{material_id}/brand")
async def update_material_brand(
material_id: int,
brand_data: BrandUpdate,
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user)
):
"""자재의 브랜드 정보를 업데이트합니다."""
try:
# 자재 존재 여부 확인
result = db.execute(
text("SELECT id FROM materials WHERE id = :material_id"),
{"material_id": material_id}
)
material = result.fetchone()
if not material:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="자재를 찾을 수 없습니다."
)
# 브랜드 업데이트
db.execute(
text("""
UPDATE materials
SET brand = :brand,
updated_by = :updated_by
WHERE id = :material_id
"""),
{
"brand": brand_data.brand.strip(),
"updated_by": current_user.get("username", "unknown"),
"material_id": material_id
}
)
db.commit()
return {
"success": True,
"message": "브랜드가 성공적으로 업데이트되었습니다.",
"material_id": material_id,
"brand": brand_data.brand.strip()
}
except Exception as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"브랜드 업데이트 실패: {str(e)}"
)
@router.patch("/{material_id}/user-requirement")
async def update_material_user_requirement(
material_id: int,
requirement_data: UserRequirementUpdate,
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user)
):
"""자재의 사용자 요구사항을 업데이트합니다."""
try:
# 자재 존재 여부 확인
result = db.execute(
text("SELECT id FROM materials WHERE id = :material_id"),
{"material_id": material_id}
)
material = result.fetchone()
if not material:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="자재를 찾을 수 없습니다."
)
# 사용자 요구사항 업데이트
db.execute(
text("""
UPDATE materials
SET user_requirement = :user_requirement,
updated_by = :updated_by
WHERE id = :material_id
"""),
{
"user_requirement": requirement_data.user_requirement.strip(),
"updated_by": current_user.get("username", "unknown"),
"material_id": material_id
}
)
db.commit()
return {
"success": True,
"message": "사용자 요구사항이 성공적으로 업데이트되었습니다.",
"material_id": material_id,
"user_requirement": requirement_data.user_requirement.strip()
}
except Exception as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"사용자 요구사항 업데이트 실패: {str(e)}"
)
@router.get("/{material_id}")
async def get_material(
material_id: int,
db: Session = Depends(get_db),
current_user: dict = Depends(get_current_user)
):
"""자재 정보를 조회합니다."""
try:
result = db.execute(
text("""
SELECT id, original_description, classified_category,
brand, user_requirement, created_at, updated_by
FROM materials
WHERE id = :material_id
"""),
{"material_id": material_id}
)
material = result.fetchone()
if not material:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="자재를 찾을 수 없습니다."
)
return {
"id": material.id,
"original_description": material.original_description,
"classified_category": material.classified_category,
"brand": material.brand,
"user_requirement": material.user_requirement,
"created_at": material.created_at,
"updated_by": material.updated_by
}
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"자재 조회 실패: {str(e)}"
)