- 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>
212 lines
6.6 KiB
Python
212 lines
6.6 KiB
Python
"""
|
|
프랜차이즈 관리 라우터
|
|
- 프랜차이즈 정보 조회
|
|
- 프랜차이즈 수정
|
|
- 프랜차이즈 전체 통계
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import func
|
|
from datetime import datetime, date
|
|
|
|
from database import get_db
|
|
from models import Franchise, Store, User, WaitingList, DailyClosing, Member
|
|
from schemas import Franchise as FranchiseSchema, FranchiseUpdate
|
|
from auth import get_current_user, require_franchise_admin
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/", response_model=FranchiseSchema)
|
|
async def get_franchise(
|
|
current_user: User = Depends(require_franchise_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""프랜차이즈 정보 조회
|
|
|
|
Returns:
|
|
Franchise: 프랜차이즈 정보
|
|
"""
|
|
franchise = db.query(Franchise).filter(
|
|
Franchise.id == current_user.franchise_id
|
|
).first()
|
|
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
return franchise
|
|
|
|
|
|
@router.put("/{franchise_id}", response_model=FranchiseSchema)
|
|
async def update_franchise(
|
|
franchise_id: int,
|
|
franchise_update: FranchiseUpdate,
|
|
current_user: User = Depends(require_franchise_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""프랜차이즈 정보 수정
|
|
|
|
Args:
|
|
franchise_id: 프랜차이즈 ID
|
|
franchise_update: 수정할 프랜차이즈 정보
|
|
|
|
Returns:
|
|
Franchise: 수정된 프랜차이즈 정보
|
|
"""
|
|
# 권한 체크: 자신의 프랜차이즈만 수정 가능
|
|
if current_user.franchise_id != franchise_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="다른 프랜차이즈를 수정할 권한이 없습니다"
|
|
)
|
|
|
|
franchise = db.query(Franchise).filter(Franchise.id == franchise_id).first()
|
|
|
|
if not franchise:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="프랜차이즈를 찾을 수 없습니다"
|
|
)
|
|
|
|
# 수정
|
|
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.get("/stats")
|
|
async def get_franchise_stats(
|
|
current_user: User = Depends(require_franchise_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""프랜차이즈 전체 통계 조회
|
|
|
|
Returns:
|
|
dict: 프랜차이즈 전체 통계 정보
|
|
"""
|
|
franchise_id = current_user.franchise_id
|
|
|
|
# 매장 수
|
|
total_stores = db.query(func.count(Store.id)).filter(
|
|
Store.franchise_id == franchise_id,
|
|
Store.is_active == True
|
|
).scalar()
|
|
|
|
# 활성 매장 수
|
|
active_stores = db.query(func.count(Store.id)).filter(
|
|
Store.franchise_id == franchise_id,
|
|
Store.is_active == True
|
|
).scalar()
|
|
|
|
# 오늘 날짜
|
|
today = date.today()
|
|
|
|
# 프랜차이즈 전체 매장의 오늘 통계 Query
|
|
query = db.query(Store).filter(
|
|
Store.franchise_id == franchise_id,
|
|
Store.is_active == True
|
|
)
|
|
|
|
# franchise_manager인 경우 관리 매장만 필터링
|
|
if current_user.role == 'franchise_manager':
|
|
managed_ids = [s.id for s in current_user.managed_stores]
|
|
if not managed_ids:
|
|
# 관리 매장이 없는 경우 빈 결과 반환
|
|
return {
|
|
'franchise_id': franchise_id,
|
|
'total_stores': 0,
|
|
'active_stores': 0,
|
|
'total_users': 0, # Note: This might need adjustment if users are also scoped
|
|
'total_members': 0,
|
|
'today_stats': {
|
|
'total_waiting': 0,
|
|
'total_attended': 0,
|
|
'total_cancelled': 0
|
|
},
|
|
'current_waiting': 0,
|
|
'stores': []
|
|
}
|
|
query = query.filter(Store.id.in_(managed_ids))
|
|
|
|
stores = query.all()
|
|
|
|
store_ids = [store.id for store in stores]
|
|
|
|
# 오늘의 대기 통계 (모든 매장 합계)
|
|
today_stats = db.query(
|
|
func.coalesce(func.sum(DailyClosing.total_waiting), 0).label('total_waiting'),
|
|
func.coalesce(func.sum(DailyClosing.total_attended), 0).label('total_attended'),
|
|
func.coalesce(func.sum(DailyClosing.total_cancelled), 0).label('total_cancelled')
|
|
).filter(
|
|
DailyClosing.store_id.in_(store_ids),
|
|
DailyClosing.business_date == today
|
|
).first()
|
|
|
|
# 현재 대기 중인 고객 수 (모든 매장 합계)
|
|
current_waiting = db.query(func.count(WaitingList.id)).filter(
|
|
WaitingList.store_id.in_(store_ids),
|
|
WaitingList.status == 'waiting'
|
|
).scalar()
|
|
|
|
# 총 사용자 수
|
|
total_users = db.query(func.count(User.id)).filter(
|
|
User.franchise_id == franchise_id
|
|
).scalar()
|
|
|
|
# 총 회원 수 (모든 매장 합계)
|
|
total_members = db.query(func.count(Member.id)).filter(
|
|
Member.store_id.in_(store_ids)
|
|
).scalar() if store_ids else 0
|
|
|
|
# 매장별 간단한 통계
|
|
store_stats = []
|
|
for store in stores:
|
|
# 매장의 오늘 통계
|
|
store_today = db.query(DailyClosing).filter(
|
|
DailyClosing.store_id == store.id,
|
|
DailyClosing.business_date == today
|
|
).first()
|
|
|
|
# 매장의 현재 대기 수
|
|
store_waiting = db.query(func.count(WaitingList.id)).filter(
|
|
WaitingList.store_id == store.id,
|
|
WaitingList.status == 'waiting'
|
|
).scalar()
|
|
|
|
store_stats.append({
|
|
'store_id': store.id,
|
|
'store_name': store.name,
|
|
'store_code': store.code,
|
|
'current_waiting': store_waiting,
|
|
'today_total': store_today.total_waiting if store_today else 0,
|
|
'today_attended': store_today.total_attended if store_today else 0,
|
|
'today_cancelled': store_today.total_cancelled if store_today else 0,
|
|
'is_open': store_today.is_closed == False if store_today else False
|
|
})
|
|
|
|
return {
|
|
'franchise_id': franchise_id,
|
|
'total_stores': total_stores,
|
|
'active_stores': active_stores,
|
|
'total_users': total_users,
|
|
'total_members': total_members,
|
|
'today_stats': {
|
|
'total_waiting': today_stats.total_waiting if today_stats else 0,
|
|
'total_attended': today_stats.total_attended if today_stats else 0,
|
|
'total_cancelled': today_stats.total_cancelled if today_stats else 0
|
|
},
|
|
'current_waiting': current_waiting,
|
|
'stores': store_stats
|
|
}
|