- Add main application files (main.py, models.py, schemas.py, etc.) - Add routers for all features (waiting, attendance, members, etc.) - Add HTML templates for admin and user interfaces - Add migration scripts and utility files - Add Docker configuration - Add documentation files - Add .gitignore to exclude database and cache files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
792 lines
24 KiB
Python
792 lines
24 KiB
Python
"""
|
|
시스템 관리자 라우터
|
|
- 프랜차이즈 CRUD
|
|
- 프랜차이즈 관리자 생성
|
|
- 전체 시스템 통계
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session, joinedload
|
|
from sqlalchemy import func
|
|
from datetime import datetime
|
|
from typing import List
|
|
|
|
from database import get_db
|
|
from models import Franchise, Store, User, Member, DailyClosing
|
|
from schemas import (
|
|
Franchise as FranchiseSchema,
|
|
FranchiseCreate,
|
|
FranchiseUpdate,
|
|
User as UserSchema,
|
|
UserCreate,
|
|
UserUpdate,
|
|
Store as StoreSchema,
|
|
StoreCreate,
|
|
UserListResponse,
|
|
StoreListResponse,
|
|
MemberListResponse
|
|
)
|
|
from auth import require_system_admin, get_password_hash
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/franchises", response_model=List[FranchiseSchema])
|
|
async def get_all_franchises(
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""모든 프랜차이즈 조회"""
|
|
franchises = db.query(Franchise).all()
|
|
return franchises
|
|
|
|
|
|
@router.get("/franchises/{franchise_id}", response_model=FranchiseSchema)
|
|
async def get_franchise(
|
|
franchise_id: int,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""특정 프랜차이즈 조회"""
|
|
franchise = db.query(Franchise).filter(Franchise.id == franchise_id).first()
|
|
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
return franchise
|
|
|
|
|
|
@router.get("/franchises/{franchise_id}/stores", response_model=List[StoreSchema])
|
|
async def get_franchise_stores(
|
|
franchise_id: int,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""특정 프랜차이즈의 매장 목록 조회"""
|
|
franchise = db.query(Franchise).filter(Franchise.id == franchise_id).first()
|
|
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
stores = db.query(Store).filter(Store.franchise_id == franchise_id).all()
|
|
return stores
|
|
|
|
|
|
@router.get("/franchises/{franchise_id}/users", response_model=List[UserSchema])
|
|
async def get_franchise_users(
|
|
franchise_id: int,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""특정 프랜차이즈의 사용자 목록 조회"""
|
|
franchise = db.query(Franchise).filter(Franchise.id == franchise_id).first()
|
|
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
users = db.query(User).filter(User.franchise_id == franchise_id).all()
|
|
return users
|
|
|
|
|
|
@router.get("/franchises/{franchise_id}/stats")
|
|
async def get_franchise_stats(
|
|
franchise_id: int,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""특정 프랜차이즈의 통계 조회"""
|
|
franchise = db.query(Franchise).filter(Franchise.id == franchise_id).first()
|
|
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
from datetime import date
|
|
|
|
# 매장 수
|
|
total_stores = db.query(func.count(Store.id)).filter(
|
|
Store.franchise_id == franchise_id
|
|
).scalar()
|
|
|
|
# 활성 매장 수
|
|
active_stores = db.query(func.count(Store.id)).filter(
|
|
Store.franchise_id == franchise_id,
|
|
Store.is_active == True
|
|
).scalar()
|
|
|
|
# 사용자 수
|
|
total_users = db.query(func.count(User.id)).filter(
|
|
User.franchise_id == franchise_id
|
|
).scalar()
|
|
|
|
# 오늘 날짜
|
|
today = date.today()
|
|
|
|
# 프랜차이즈 전체 매장의 오늘 통계
|
|
stores = db.query(Store).filter(
|
|
Store.franchise_id == franchise_id,
|
|
Store.is_active == True
|
|
).all()
|
|
|
|
store_ids = [store.id for store in stores]
|
|
|
|
# 총 회원 수 (모든 매장 합계)
|
|
total_members = db.query(func.count(Member.id)).filter(
|
|
Member.store_id.in_(store_ids)
|
|
).scalar() if store_ids else 0
|
|
|
|
return {
|
|
'franchise_id': franchise_id,
|
|
'total_stores': total_stores,
|
|
'active_stores': active_stores,
|
|
'total_users': total_users,
|
|
'total_members': total_members
|
|
}
|
|
|
|
|
|
@router.post("/franchises", response_model=FranchiseSchema, status_code=status.HTTP_201_CREATED)
|
|
async def create_franchise(
|
|
franchise: FranchiseCreate,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""새 프랜차이즈 생성"""
|
|
# 코드 중복 체크
|
|
existing = db.query(Franchise).filter(Franchise.code == franchise.code).first()
|
|
if existing:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"프랜차이즈 코드 '{franchise.code}'가 이미 존재합니다"
|
|
)
|
|
|
|
# 프랜차이즈 생성
|
|
new_franchise = Franchise(
|
|
name=franchise.name,
|
|
code=franchise.code,
|
|
is_active=True,
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now()
|
|
)
|
|
|
|
db.add(new_franchise)
|
|
db.commit()
|
|
db.refresh(new_franchise)
|
|
|
|
return new_franchise
|
|
|
|
|
|
@router.put("/franchises/{franchise_id}", response_model=FranchiseSchema)
|
|
async def update_franchise(
|
|
franchise_id: int,
|
|
franchise_update: FranchiseUpdate,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""프랜차이즈 정보 수정"""
|
|
franchise = db.query(Franchise).filter(Franchise.id == franchise_id).first()
|
|
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
# 코드 중복 체크 (코드 변경 시)
|
|
if franchise_update.code and franchise_update.code != franchise.code:
|
|
existing = db.query(Franchise).filter(Franchise.code == franchise_update.code).first()
|
|
if existing:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"프랜차이즈 코드 '{franchise_update.code}'가 이미 존재합니다"
|
|
)
|
|
|
|
# 수정
|
|
update_data = franchise_update.dict(exclude_unset=True)
|
|
for key, value in update_data.items():
|
|
setattr(franchise, key, value)
|
|
|
|
franchise.updated_at = datetime.now()
|
|
|
|
db.commit()
|
|
db.refresh(franchise)
|
|
|
|
return franchise
|
|
|
|
|
|
@router.delete("/franchises/{franchise_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_franchise(
|
|
franchise_id: int,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""프랜차이즈 삭제 (비활성화)"""
|
|
franchise = db.query(Franchise).filter(Franchise.id == franchise_id).first()
|
|
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
# 비활성화
|
|
franchise.is_active = False
|
|
franchise.updated_at = datetime.now()
|
|
|
|
db.commit()
|
|
|
|
return None
|
|
|
|
|
|
@router.post("/franchises/{franchise_id}/activate", response_model=FranchiseSchema)
|
|
async def activate_franchise(
|
|
franchise_id: int,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""프랜차이즈 활성화"""
|
|
franchise = db.query(Franchise).filter(Franchise.id == franchise_id).first()
|
|
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
franchise.is_active = True
|
|
franchise.updated_at = datetime.now()
|
|
|
|
db.commit()
|
|
db.refresh(franchise)
|
|
|
|
return franchise
|
|
|
|
|
|
@router.post("/franchises/{franchise_id}/admin", response_model=UserSchema, status_code=status.HTTP_201_CREATED)
|
|
async def create_franchise_admin(
|
|
franchise_id: int,
|
|
user_create: UserCreate,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""프랜차이즈 관리자 생성"""
|
|
# 프랜차이즈 존재 확인
|
|
franchise = db.query(Franchise).filter(Franchise.id == franchise_id).first()
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
# 사용자명 중복 확인
|
|
existing_user = db.query(User).filter(User.username == user_create.username).first()
|
|
if existing_user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"사용자명 '{user_create.username}'가 이미 존재합니다"
|
|
)
|
|
|
|
# 프랜차이즈 관리자만 생성 가능
|
|
if user_create.role != "franchise_admin":
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="이 엔드포인트는 프랜차이즈 관리자 생성 전용입니다"
|
|
)
|
|
|
|
# 사용자 생성
|
|
password_hash = get_password_hash(user_create.password)
|
|
new_user = User(
|
|
username=user_create.username,
|
|
password_hash=password_hash,
|
|
role="franchise_admin",
|
|
franchise_id=franchise_id,
|
|
store_id=None,
|
|
is_active=True,
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now()
|
|
)
|
|
|
|
db.add(new_user)
|
|
db.commit()
|
|
db.refresh(new_user)
|
|
|
|
return new_user
|
|
|
|
|
|
@router.get("/stats")
|
|
async def get_system_stats(
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""전체 시스템 통계"""
|
|
# 프랜차이즈 수
|
|
total_franchises = db.query(func.count(Franchise.id)).scalar()
|
|
active_franchises = db.query(func.count(Franchise.id)).filter(
|
|
Franchise.is_active == True
|
|
).scalar()
|
|
|
|
# 매장 수
|
|
total_stores = db.query(func.count(Store.id)).scalar()
|
|
active_stores = db.query(func.count(Store.id)).filter(
|
|
Store.is_active == True
|
|
).scalar()
|
|
|
|
# 사용자 수
|
|
total_users = db.query(func.count(User.id)).scalar()
|
|
active_users = db.query(func.count(User.id)).filter(
|
|
User.is_active == True
|
|
).scalar()
|
|
|
|
# 회원 수
|
|
total_members = db.query(func.count(Member.id)).scalar()
|
|
|
|
# 프랜차이즈별 통계
|
|
franchises = db.query(Franchise).all()
|
|
franchise_stats = []
|
|
|
|
for franchise in franchises:
|
|
# 프랜차이즈의 매장 수
|
|
stores_count = db.query(func.count(Store.id)).filter(
|
|
Store.franchise_id == franchise.id
|
|
).scalar()
|
|
|
|
# 프랜차이즈의 활성 매장 수
|
|
active_stores_count = db.query(func.count(Store.id)).filter(
|
|
Store.franchise_id == franchise.id,
|
|
Store.is_active == True
|
|
).scalar()
|
|
|
|
# 프랜차이즈의 사용자 수
|
|
users_count = db.query(func.count(User.id)).filter(
|
|
User.franchise_id == franchise.id
|
|
).scalar()
|
|
|
|
# 프랜차이즈의 매장 ID 목록
|
|
store_ids = [s.id for s in db.query(Store.id).filter(
|
|
Store.franchise_id == franchise.id
|
|
).all()]
|
|
|
|
# 프랜차이즈의 회원 수
|
|
members_count = db.query(func.count(Member.id)).filter(
|
|
Member.store_id.in_(store_ids)
|
|
).scalar() if store_ids else 0
|
|
|
|
franchise_stats.append({
|
|
"franchise_id": franchise.id,
|
|
"franchise_name": franchise.name,
|
|
"franchise_code": franchise.code,
|
|
"is_active": franchise.is_active,
|
|
"stores_count": stores_count,
|
|
"active_stores_count": active_stores_count,
|
|
"users_count": users_count,
|
|
"members_count": members_count
|
|
})
|
|
|
|
return {
|
|
"total_franchises": total_franchises,
|
|
"active_franchises": active_franchises,
|
|
"total_stores": total_stores,
|
|
"active_stores": active_stores,
|
|
"total_users": total_users,
|
|
"active_users": active_users,
|
|
"total_members": total_members,
|
|
"franchises": franchise_stats
|
|
}
|
|
|
|
|
|
# ========== 매장 관리 (Superadmin) ==========
|
|
|
|
@router.post("/franchises/{franchise_id}/stores", response_model=StoreSchema, status_code=status.HTTP_201_CREATED)
|
|
async def create_store_for_franchise(
|
|
franchise_id: int,
|
|
store_create: StoreCreate,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""특정 프랜차이즈의 매장 생성 (Superadmin 전용)"""
|
|
# 프랜차이즈 존재 확인
|
|
franchise = db.query(Franchise).filter(Franchise.id == franchise_id).first()
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
# 매장 코드 자동 생성
|
|
prefix = franchise.code[0] if franchise.code else "S"
|
|
|
|
# 해당 프랜차이즈의 기존 매장 중 같은 prefix를 가진 매장 코드에서 가장 큰 번호 찾기
|
|
stores = db.query(Store).filter(Store.franchise_id == franchise_id).all()
|
|
|
|
max_number = 0
|
|
for store in stores:
|
|
if store.code.startswith(prefix) and len(store.code) > 1:
|
|
try:
|
|
number = int(store.code[1:])
|
|
if number > max_number:
|
|
max_number = number
|
|
except ValueError:
|
|
continue
|
|
|
|
# 새로운 매장 코드 생성 (예: S001, S002, S003...)
|
|
new_code = f"{prefix}{str(max_number + 1).zfill(3)}"
|
|
|
|
# 매장 생성
|
|
new_store = Store(
|
|
franchise_id=franchise_id,
|
|
name=store_create.name,
|
|
code=new_code,
|
|
is_active=True
|
|
)
|
|
|
|
db.add(new_store)
|
|
db.commit()
|
|
db.refresh(new_store)
|
|
|
|
return new_store
|
|
|
|
|
|
@router.post("/stores/{store_id}/activate", response_model=StoreSchema)
|
|
async def activate_store(
|
|
store_id: int,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""매장 활성화 (Superadmin 전용)"""
|
|
store = db.query(Store).filter(Store.id == store_id).first()
|
|
|
|
if not store:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="매장을 찾을 수 없습니다"
|
|
)
|
|
|
|
store.is_active = True
|
|
db.commit()
|
|
db.refresh(store)
|
|
|
|
return store
|
|
|
|
|
|
@router.post("/stores/{store_id}/deactivate", response_model=StoreSchema)
|
|
async def deactivate_store(
|
|
store_id: int,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""매장 비활성화 (Superadmin 전용)"""
|
|
store = db.query(Store).filter(Store.id == store_id).first()
|
|
|
|
if not store:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="매장을 찾을 수 없습니다"
|
|
)
|
|
|
|
store.is_active = False
|
|
db.commit()
|
|
db.refresh(store)
|
|
|
|
return store
|
|
|
|
|
|
# ========== 사용자 관리 (Superadmin) ==========
|
|
|
|
@router.post("/franchises/{franchise_id}/users", response_model=UserSchema, status_code=status.HTTP_201_CREATED)
|
|
async def create_user_for_franchise(
|
|
franchise_id: int,
|
|
user_create: UserCreate,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""특정 프랜차이즈의 사용자 생성 (Superadmin 전용)"""
|
|
# 프랜차이즈 존재 확인
|
|
franchise = db.query(Franchise).filter(Franchise.id == franchise_id).first()
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
# 사용자명 중복 확인
|
|
existing_user = db.query(User).filter(User.username == user_create.username).first()
|
|
if existing_user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="이미 존재하는 사용자명입니다"
|
|
)
|
|
|
|
# 역할 검증
|
|
if user_create.role not in ['franchise_admin', 'store_admin', 'franchise_manager']:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="올바르지 않은 역할입니다."
|
|
)
|
|
|
|
# 매장 관리자인 경우 매장 ID 필수
|
|
if user_create.role == 'store_admin' and not user_create.store_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="매장 관리자는 매장 ID가 필요합니다"
|
|
)
|
|
|
|
# 매장 ID가 있는 경우 해당 매장이 프랜차이즈에 속하는지 확인
|
|
if user_create.store_id:
|
|
store = db.query(Store).filter(
|
|
Store.id == user_create.store_id,
|
|
Store.franchise_id == franchise_id
|
|
).first()
|
|
|
|
if not store:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="매장을 찾을 수 없거나 해당 프랜차이즈에 속하지 않습니다"
|
|
)
|
|
|
|
# 사용자 생성
|
|
password_hash = get_password_hash(user_create.password)
|
|
|
|
new_user = User(
|
|
username=user_create.username,
|
|
password_hash=password_hash,
|
|
role=user_create.role,
|
|
franchise_id=franchise_id,
|
|
store_id=user_create.store_id,
|
|
is_active=True,
|
|
created_at=datetime.now(),
|
|
updated_at=datetime.now()
|
|
)
|
|
|
|
# 중간 관리자의 매장 권한 설정
|
|
if user_create.role == 'franchise_manager' and user_create.managed_store_ids:
|
|
stores = db.query(Store).filter(
|
|
Store.id.in_(user_create.managed_store_ids),
|
|
Store.franchise_id == franchise_id
|
|
).all()
|
|
|
|
if len(stores) != len(user_create.managed_store_ids):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="일부 매장을 찾을 수 없거나 해당 프랜차이즈에 속하지 않습니다"
|
|
)
|
|
new_user.managed_stores = stores
|
|
|
|
db.add(new_user)
|
|
db.commit()
|
|
db.refresh(new_user)
|
|
|
|
return new_user
|
|
|
|
|
|
@router.put("/users/{user_id}", response_model=UserSchema)
|
|
async def update_user(
|
|
user_id: int,
|
|
user_update: UserUpdate,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""사용자 정보 수정 (Superadmin 전용)"""
|
|
user = db.query(User).filter(User.id == user_id).first()
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="사용자를 찾을 수 없습니다"
|
|
)
|
|
|
|
# 사용자명 변경 시 중복 확인
|
|
if user_update.username and user_update.username != user.username:
|
|
existing_user = db.query(User).filter(User.username == user_update.username).first()
|
|
if existing_user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="이미 존재하는 사용자명입니다"
|
|
)
|
|
|
|
# 역할 변경 시 검증
|
|
if user_update.role and user_update.role not in ['franchise_admin', 'store_admin', 'franchise_manager']:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="올바르지 않은 역할입니다."
|
|
)
|
|
|
|
# 매장 ID 변경 시 검증
|
|
if user_update.store_id:
|
|
store = db.query(Store).filter(Store.id == user_update.store_id).first()
|
|
if not store:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="매장을 찾을 수 없습니다"
|
|
)
|
|
|
|
# 수정
|
|
update_data = user_update.dict(exclude_unset=True, exclude={'password', 'managed_store_ids'})
|
|
for key, value in update_data.items():
|
|
setattr(user, key, value)
|
|
|
|
# 중간 관리자의 매장 권한 수정
|
|
if user_update.role == 'franchise_manager' and user_update.managed_store_ids is not None:
|
|
stores = db.query(Store).filter(
|
|
Store.id.in_(user_update.managed_store_ids),
|
|
Store.franchise_id == user.franchise_id
|
|
).all()
|
|
user.managed_stores = stores
|
|
elif user_update.role != 'franchise_manager':
|
|
user.managed_stores = []
|
|
|
|
|
|
# 비밀번호 변경이 있는 경우
|
|
if user_update.password:
|
|
user.password_hash = get_password_hash(user_update.password)
|
|
|
|
user.updated_at = datetime.now()
|
|
|
|
db.commit()
|
|
db.refresh(user)
|
|
|
|
return user
|
|
|
|
|
|
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def deactivate_user(
|
|
user_id: int,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""사용자 비활성화 (Superadmin 전용)"""
|
|
# 자기 자신은 비활성화할 수 없음
|
|
if user_id == current_user.id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="자기 자신을 비활성화할 수 없습니다"
|
|
)
|
|
|
|
user = db.query(User).filter(User.id == user_id).first()
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="사용자를 찾을 수 없습니다"
|
|
)
|
|
|
|
user.is_active = False
|
|
user.updated_at = datetime.now()
|
|
|
|
db.commit()
|
|
|
|
return None
|
|
|
|
|
|
@router.post("/users/{user_id}/activate", response_model=UserSchema)
|
|
async def activate_user(
|
|
user_id: int,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""사용자 활성화 (Superadmin 전용)"""
|
|
user = db.query(User).filter(User.id == user_id).first()
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="사용자를 찾을 수 없습니다"
|
|
)
|
|
|
|
user.is_active = True
|
|
user.updated_at = datetime.now()
|
|
|
|
db.commit()
|
|
db.refresh(user)
|
|
|
|
return user
|
|
|
|
|
|
@router.get("/users", response_model=List[UserListResponse])
|
|
async def get_all_users(
|
|
skip: int = 0,
|
|
limit: int = 1000,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""전체 사용자 조회 (System Admin)"""
|
|
users = db.query(User).options(
|
|
joinedload(User.franchise),
|
|
joinedload(User.store)
|
|
).offset(skip).limit(limit).all()
|
|
|
|
response = []
|
|
for user in users:
|
|
user_dict = UserListResponse.from_orm(user)
|
|
if user.franchise:
|
|
user_dict.franchise_name = user.franchise.name
|
|
if user.store:
|
|
user_dict.store_name = user.store.name
|
|
response.append(user_dict)
|
|
|
|
return response
|
|
|
|
|
|
@router.get("/stores", response_model=List[StoreListResponse])
|
|
async def get_all_stores(
|
|
skip: int = 0,
|
|
limit: int = 1000,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""전체 매장 조회 (System Admin)"""
|
|
stores = db.query(Store).options(
|
|
joinedload(Store.franchise)
|
|
).offset(skip).limit(limit).all()
|
|
|
|
response = []
|
|
for store in stores:
|
|
store_dict = StoreListResponse.from_orm(store)
|
|
if store.franchise:
|
|
store_dict.franchise_name = store.franchise.name
|
|
response.append(store_dict)
|
|
|
|
return response
|
|
|
|
|
|
@router.get("/members", response_model=List[MemberListResponse])
|
|
async def search_members(
|
|
q: str = None,
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
current_user: User = Depends(require_system_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""회원 검색 및 조회 (System Admin)"""
|
|
query = db.query(Member).options(
|
|
joinedload(Member.store).joinedload(Store.franchise)
|
|
)
|
|
|
|
if q:
|
|
query = query.filter(
|
|
(Member.name.ilike(f"%{q}%")) |
|
|
(Member.phone.ilike(f"%{q}%"))
|
|
)
|
|
|
|
members = query.order_by(Member.created_at.desc()).offset(skip).limit(limit).all()
|
|
|
|
response = []
|
|
for member in members:
|
|
member_dict = MemberListResponse.from_orm(member)
|
|
if member.store:
|
|
member_dict.store_name = member.store.name
|
|
if member.store.franchise:
|
|
member_dict.franchise_name = member.store.franchise.name
|
|
response.append(member_dict)
|
|
|
|
return response
|