Files
TK-BOM-Project/backend/app/auth/signup_routes.py
Hyungi Ahn e27020ae9b feat: 구매신청 기능 완성 및 SUPPORT/SPECIAL 카테고리 개선
- 모든 카테고리 구매신청 기능 완성 (PIPE, FITTING, VALVE, FLANGE, GASKET, BOLT, SUPPORT, SPECIAL, UNKNOWN)
- 구매신청 완료 항목: 회색 배경, 체크박스 비활성화, '구매신청완료' 배지 표시
- 전체 선택/구매신청 시 이미 구매신청된 항목 자동 제외
- 구매신청 quantity 타입 에러 수정 (문자열 -> 정수 변환)

SUPPORT 카테고리 (구 U-BOLT):
- U-BOLT -> SUPPORT로 카테고리명 변경
- 클램프, 유볼트, 우레탄블럭슈 분류 개선
- 테이블 헤더: 선택-종류-타입-크기-디스크립션-추가요구-사용자요구-수량
- 크기 정보 main_nom 필드에서 가져오기 (배관 인치)
- 엑셀 내보내기 형식 조정

SPECIAL 카테고리:
- SPECIAL 키워드 자재 자동 분류 (SPECIFICATION 제외)
- 파일 업로드 시 SPECIAL 카테고리 처리 로직 추가
- 도면번호 필드 추가 (drawing_name, line_no)
- 타입 필드: 크기/스케줄/재질 제외한 핵심 정보 표시
- 엑셀 DWG_NAME, LINE_NUM 컬럼 파싱 및 저장

FITTING 카테고리:
- 테이블 컬럼 너비 조정 (선택 2%, 종류 8.5%, 수량 12%)

구매신청 관리:
- 엑셀 재다운로드 형식 개선 (BOM 페이지와 동일한 형식)
- 그룹화된 자재 정보 포함하여 저장 및 다운로드
2025-10-14 12:39:25 +09:00

305 lines
9.6 KiB
Python

"""
회원가입 요청 및 관리자 승인 API
"""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from sqlalchemy import text
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
from ..database import get_db
from .auth_service import AuthService
from .models import UserRepository
from .middleware import get_current_user
from ..utils.logger import get_logger
logger = get_logger(__name__)
router = APIRouter(prefix="/auth", tags=["signup"])
class SignupRequest(BaseModel):
username: str
password: str
name: str
email: Optional[str] = None
department: Optional[str] = None
position: Optional[str] = None
phone: Optional[str] = None
reason: Optional[str] = None # 가입 사유
@router.post("/signup-request")
async def signup_request(
signup_data: SignupRequest,
db: Session = Depends(get_db)
):
"""
회원가입 요청 (관리자 승인 대기)
Args:
signup_data: 가입 신청 정보
Returns:
dict: 요청 결과
"""
try:
# 중복 사용자명 확인
user_repo = UserRepository(db)
existing_user = user_repo.find_by_username(signup_data.username)
if existing_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="이미 존재하는 사용자명입니다"
)
# 중복 이메일 확인
if signup_data.email:
check_email = text("SELECT id FROM users WHERE email = :email")
existing_email = db.execute(check_email, {"email": signup_data.email}).fetchone()
if existing_email:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="이미 등록된 이메일입니다"
)
# 비밀번호 해싱
import bcrypt
hashed_password = bcrypt.hashpw(
signup_data.password.encode('utf-8'),
bcrypt.gensalt()
).decode('utf-8')
# 승인 대기 상태로 사용자 생성
new_user = user_repo.create_user({
'username': signup_data.username,
'password': hashed_password, # 필드명은 'password'
'name': signup_data.name,
'email': signup_data.email,
'access_level': 'worker', # 기본 레벨 (승인 시 변경 가능)
'department': signup_data.department,
'position': signup_data.position,
'phone': signup_data.phone,
'role': 'user',
'is_active': False, # 하위 호환성
'status': 'pending' # 새로운 status 체계: 승인 대기
})
# 가입 사유 저장 (notes 컬럼 활용)
if signup_data.reason:
update_notes = text("UPDATE users SET notes = :reason WHERE user_id = :user_id")
db.execute(update_notes, {"reason": signup_data.reason, "user_id": new_user.user_id})
db.commit()
return {
"success": True,
"message": "회원가입 요청이 전송되었습니다. 관리자 승인 후 이용 가능합니다.",
"user_id": new_user.user_id,
"username": new_user.username,
"status": "pending_approval"
}
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"회원가입 요청 실패: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"회원가입 요청 처리 중 오류가 발생했습니다: {str(e)}"
)
@router.get("/signup-requests")
async def get_signup_requests(
current_user: dict = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""
회원가입 요청 목록 조회 (관리자 전용)
Returns:
dict: 승인 대기 중인 사용자 목록
"""
try:
# 관리자 권한 확인
if current_user.get('role') not in ['admin', 'system']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="관리자만 접근 가능합니다"
)
# 승인 대기 중인 사용자 조회 (status='pending'인 사용자)
query = text("""
SELECT
user_id, username, name, email, department, position,
phone, created_at, role, is_active, status
FROM users
WHERE status = 'pending'
ORDER BY created_at DESC
""")
results = db.execute(query).fetchall()
pending_users = []
for row in results:
pending_users.append({
"user_id": row.user_id,
"id": row.user_id, # 호환성을 위해 둘 다 제공
"username": row.username,
"name": row.name,
"email": row.email,
"department": row.department,
"position": row.position,
"phone": row.phone,
"role": row.role,
"created_at": row.created_at.isoformat() if row.created_at else None,
"requested_at": row.created_at.isoformat() if row.created_at else None,
"is_active": row.is_active
})
return {
"success": True,
"requests": pending_users,
"count": len(pending_users)
}
except HTTPException:
raise
except Exception as e:
logger.error(f"가입 요청 목록 조회 실패: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"가입 요청 목록 조회 실패: {str(e)}"
)
@router.post("/approve-signup/{user_id}")
async def approve_signup(
user_id: int,
access_level: str = 'worker',
current_user: dict = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""
회원가입 승인 (관리자 전용)
Args:
user_id: 승인할 사용자 ID
access_level: 부여할 접근 레벨 (worker, manager, admin)
Returns:
dict: 승인 결과
"""
try:
# 관리자 권한 확인
if current_user.get('role') not in ['admin', 'system']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="관리자만 접근 가능합니다"
)
# 사용자 활성화 및 접근 레벨 설정
update_query = text("""
UPDATE users
SET is_active = TRUE,
status = 'active',
access_level = :access_level,
updated_at = CURRENT_TIMESTAMP
WHERE user_id = :user_id AND status = 'pending'
RETURNING user_id as id, username, name
""")
result = db.execute(update_query, {
"user_id": user_id,
"access_level": access_level
}).fetchone()
if not result:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="승인 대기 중인 사용자를 찾을 수 없습니다"
)
db.commit()
return {
"success": True,
"message": f"{result.name}님의 가입이 승인되었습니다",
"user": {
"id": result.id,
"username": result.username,
"name": result.name,
"access_level": access_level
}
}
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"가입 승인 실패: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"가입 승인 처리 중 오류가 발생했습니다: {str(e)}"
)
@router.delete("/reject-signup/{user_id}")
async def reject_signup(
user_id: int,
current_user: dict = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""
회원가입 거부 (관리자 전용)
Args:
user_id: 거부할 사용자 ID
Returns:
dict: 거부 결과
"""
try:
# 관리자 권한 확인
if current_user.get('role') not in ['admin', 'system']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="관리자만 접근 가능합니다"
)
# 승인 대기 사용자 삭제
delete_query = text("""
DELETE FROM users
WHERE user_id = :user_id AND is_active = FALSE
RETURNING username, name
""")
result = db.execute(delete_query, {"user_id": user_id}).fetchone()
if not result:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="승인 대기 중인 사용자를 찾을 수 없습니다"
)
db.commit()
return {
"success": True,
"message": f"{result.name}님의 가입 요청이 거부되었습니다"
}
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"가입 거부 실패: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"가입 거부 처리 중 오류가 발생했습니다: {str(e)}"
)