- 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>
410 lines
11 KiB
Python
410 lines
11 KiB
Python
from pydantic import BaseModel, Field
|
|
from datetime import datetime, date, time
|
|
from typing import Optional, List, Dict
|
|
|
|
# Store Settings
|
|
class StoreSettingsBase(BaseModel):
|
|
store_name: str
|
|
display_classes_count: int = 3
|
|
list_direction: str = "vertical"
|
|
rows_per_class: int = 1
|
|
admin_password: str = "1234"
|
|
max_waiting_limit: int = 50
|
|
use_max_waiting_limit: bool = True
|
|
block_last_class_registration: bool = False
|
|
auto_register_member: bool = False
|
|
business_day_start: int = 5 # 영업일 기준 시간 (0~23)
|
|
auto_closing: bool = True # 영업일 변경 시 자동 마감 및 리셋 여부
|
|
closing_action: str = "reset"
|
|
|
|
# 출석 횟수 표시 설정
|
|
attendance_count_type: str = "days"
|
|
attendance_lookback_days: int = 30
|
|
|
|
# 대기현황판 표시 설정
|
|
show_waiting_number: bool = True
|
|
mask_customer_name: bool = False
|
|
name_display_length: int = 0 # 이름 표시 자릿수 (0 = 전체 표시)
|
|
show_order_number: bool = True
|
|
board_display_order: str = "number,name,order"
|
|
|
|
# 폰트 설정
|
|
manager_font_family: str = "Nanum Gothic"
|
|
manager_font_size: str = "15px"
|
|
board_font_family: str = "Nanum Gothic"
|
|
board_font_size: str = "24px"
|
|
|
|
# 대기접수 키패드 설정
|
|
keypad_style: str = "modern" # modern, bold, dark, colorful
|
|
|
|
keypad_font_size: str = "large" # small, medium, large, xlarge
|
|
|
|
# 개점 설정
|
|
daily_opening_rule: str = "strict"
|
|
|
|
# 대기접수 완료 모달 설정
|
|
waiting_modal_timeout: int = 5
|
|
show_member_name_in_waiting_modal: bool = True
|
|
show_new_member_text_in_waiting_modal: bool = True
|
|
enable_waiting_voice_alert: bool = False
|
|
waiting_voice_message: Optional[str] = None
|
|
waiting_voice_name: Optional[str] = None
|
|
waiting_voice_rate: float = 1.0
|
|
waiting_voice_pitch: float = 1.0
|
|
|
|
# 대기관리자 화면 레이아웃 설정
|
|
waiting_manager_max_width: Optional[int] = None
|
|
|
|
class StoreSettingsCreate(StoreSettingsBase):
|
|
pass
|
|
|
|
class StoreSettingsUpdate(BaseModel):
|
|
store_name: Optional[str] = None
|
|
display_classes_count: Optional[int] = None
|
|
display_count: Optional[int] = 5
|
|
list_direction: Optional[str] = None
|
|
rows_per_class: Optional[int] = None
|
|
admin_password: Optional[str] = None
|
|
max_waiting_limit: Optional[int] = None
|
|
use_max_waiting_limit: Optional[bool] = None
|
|
block_last_class_registration: Optional[bool] = None
|
|
auto_register_member: Optional[bool] = None
|
|
business_day_start: Optional[int] = 0
|
|
auto_closing: Optional[bool] = True
|
|
closing_action: Optional[str] = "reset"
|
|
|
|
# 출석 횟수 표시 설정
|
|
attendance_count_type: Optional[str] = None
|
|
attendance_lookback_days: Optional[int] = None
|
|
|
|
# 대기현황판 표시 설정
|
|
show_waiting_number: Optional[bool] = None
|
|
mask_customer_name: Optional[bool] = None
|
|
name_display_length: Optional[int] = None
|
|
show_order_number: Optional[bool] = None
|
|
board_display_order: Optional[str] = None
|
|
|
|
# 폰트 설정
|
|
manager_font_family: Optional[str] = None
|
|
manager_font_size: Optional[str] = None
|
|
board_font_family: Optional[str] = None
|
|
board_font_size: Optional[str] = None
|
|
|
|
# 대기접수 키패드 설정
|
|
keypad_style: Optional[str] = None
|
|
keypad_font_size: Optional[str] = None
|
|
|
|
# 개점 설정
|
|
daily_opening_rule: Optional[str] = None
|
|
|
|
# 대기접수 완료 모달 설정
|
|
waiting_modal_timeout: Optional[int] = None
|
|
show_member_name_in_waiting_modal: Optional[bool] = None
|
|
show_new_member_text_in_waiting_modal: Optional[bool] = None
|
|
enable_waiting_voice_alert: Optional[bool] = None
|
|
waiting_voice_message: Optional[str] = None
|
|
waiting_voice_name: Optional[str] = None
|
|
waiting_voice_rate: Optional[float] = None
|
|
waiting_voice_pitch: Optional[float] = None
|
|
|
|
# 대기관리자 화면 레이아웃 설정
|
|
waiting_manager_max_width: Optional[int] = None
|
|
|
|
class StoreSettings(StoreSettingsBase):
|
|
id: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
# Daily Closing
|
|
class DailyClosingBase(BaseModel):
|
|
business_date: date
|
|
|
|
class DailyClosingCreate(DailyClosingBase):
|
|
pass
|
|
|
|
class DailyClosing(DailyClosingBase):
|
|
id: int
|
|
opening_time: Optional[datetime]
|
|
closing_time: Optional[datetime]
|
|
is_closed: bool
|
|
total_waiting: int
|
|
total_attended: int
|
|
total_cancelled: int
|
|
created_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
# Class Info
|
|
class ClassInfoBase(BaseModel):
|
|
class_number: int
|
|
class_name: str
|
|
start_time: time
|
|
end_time: time
|
|
max_capacity: int = 10
|
|
is_active: bool = True
|
|
weekday_schedule: Optional[Dict[str, bool]] = None
|
|
class_type: str = 'all' # weekday, weekend, all
|
|
|
|
class ClassInfoCreate(ClassInfoBase):
|
|
weekday_schedule: Dict[str, bool] = Field(default_factory=lambda: {
|
|
"mon": True,
|
|
"tue": True,
|
|
"wed": True,
|
|
"thu": True,
|
|
"fri": True,
|
|
"sat": True,
|
|
"sun": True
|
|
})
|
|
|
|
class ClassInfoUpdate(BaseModel):
|
|
class_number: Optional[int] = None
|
|
class_name: Optional[str] = None
|
|
start_time: Optional[time] = None
|
|
end_time: Optional[time] = None
|
|
max_capacity: Optional[int] = None
|
|
is_active: Optional[bool] = None
|
|
weekday_schedule: Optional[Dict[str, bool]] = None
|
|
class_type: Optional[str] = None
|
|
|
|
class ClassInfo(ClassInfoBase):
|
|
id: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
current_count: Optional[int] = 0 # 현재 대기자 수
|
|
weekday_schedule: Dict[str, bool] = Field(default_factory=lambda: {
|
|
"mon": True,
|
|
"tue": True,
|
|
"wed": True,
|
|
"thu": True,
|
|
"fri": True,
|
|
"sat": True,
|
|
"sun": True
|
|
}) # 응답에서는 항상 존재
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
# Member
|
|
class MemberBase(BaseModel):
|
|
name: str
|
|
phone: str = Field(..., pattern=r'^010\d{8}$')
|
|
barcode: Optional[str] = None
|
|
|
|
class MemberCreate(MemberBase):
|
|
pass
|
|
|
|
class MemberUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
phone: Optional[str] = None
|
|
barcode: Optional[str] = None
|
|
|
|
class Member(MemberBase):
|
|
id: int
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class MemberBulkCreate(BaseModel):
|
|
members: List[MemberBase]
|
|
|
|
# Waiting List
|
|
class WaitingListBase(BaseModel):
|
|
phone: str = Field(..., pattern=r'^010\d{8}$')
|
|
name: Optional[str] = None
|
|
|
|
class WaitingListCreate(WaitingListBase):
|
|
class_id: Optional[int] = None
|
|
person_count: Optional[int] = 1
|
|
|
|
class WaitingListResponse(BaseModel):
|
|
id: int
|
|
waiting_number: int
|
|
class_id: int
|
|
class_name: str
|
|
class_order: int
|
|
phone: str
|
|
name: Optional[str]
|
|
status: str
|
|
registered_at: datetime
|
|
message: str
|
|
last_month_attendance_count: int = 0
|
|
is_new_member: bool = False
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class WaitingList(BaseModel):
|
|
id: int
|
|
business_date: date
|
|
waiting_number: int
|
|
phone: str
|
|
name: Optional[str]
|
|
class_id: int
|
|
class_order: int
|
|
member_id: Optional[int]
|
|
is_empty_seat: bool = False
|
|
status: str
|
|
registered_at: datetime
|
|
attended_at: Optional[datetime]
|
|
cancelled_at: Optional[datetime]
|
|
call_count: int
|
|
last_called_at: Optional[datetime]
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class WaitingListDetail(WaitingList):
|
|
class_info: ClassInfo
|
|
member: Optional[Member] = None
|
|
|
|
class WaitingBoardItem(BaseModel):
|
|
id: int # 대기자 고유 ID
|
|
waiting_number: int
|
|
display_name: str # 이름 또는 폰번호 뒷자리 4자리
|
|
class_id: int
|
|
class_name: str
|
|
class_order: int
|
|
is_empty_seat: bool = False
|
|
status: str
|
|
|
|
class WaitingBoard(BaseModel):
|
|
store_name: str
|
|
business_date: date
|
|
classes: List[ClassInfo]
|
|
waiting_list: List[WaitingBoardItem]
|
|
|
|
# Waiting Management
|
|
class WaitingStatusUpdate(BaseModel):
|
|
status: str # attended, cancelled
|
|
|
|
class WaitingOrderUpdate(BaseModel):
|
|
direction: str # up, down
|
|
|
|
class WaitingClassUpdate(BaseModel):
|
|
target_class_id: int
|
|
|
|
class BatchAttendance(BaseModel):
|
|
class_id: int
|
|
|
|
class EmptySeatInsert(BaseModel):
|
|
waiting_id: int # 이 대기자 뒤에 빈 좌석 삽입
|
|
|
|
# Statistics
|
|
class DailyStatistics(BaseModel):
|
|
business_date: date
|
|
total_waiting: int
|
|
total_attended: int
|
|
total_cancelled: int
|
|
total_no_show: int
|
|
attendance_rate: float
|
|
class_statistics: List[dict]
|
|
|
|
# Franchise
|
|
class FranchiseBase(BaseModel):
|
|
name: str
|
|
code: str
|
|
|
|
class FranchiseCreate(FranchiseBase):
|
|
pass
|
|
|
|
class FranchiseUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
code: Optional[str] = None
|
|
member_type: Optional[str] = None
|
|
is_active: Optional[bool] = None
|
|
|
|
class Franchise(FranchiseBase):
|
|
id: int
|
|
member_type: str
|
|
is_active: bool
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
# Store
|
|
class StoreBase(BaseModel):
|
|
franchise_id: int
|
|
name: str
|
|
code: str
|
|
|
|
class StoreCreate(BaseModel):
|
|
name: str
|
|
# code는 자동 생성되므로 입력받지 않음
|
|
|
|
class StoreUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
code: Optional[str] = None
|
|
is_active: Optional[bool] = None
|
|
|
|
class Store(StoreBase):
|
|
id: int
|
|
is_active: bool
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
# User
|
|
class UserBase(BaseModel):
|
|
username: str
|
|
role: str # system_admin, franchise_admin, store_admin
|
|
franchise_id: Optional[int] = None
|
|
store_id: Optional[int] = None
|
|
|
|
class UserCreate(UserBase):
|
|
password: str # 평문 비밀번호 (해싱되어 저장됨)
|
|
managed_store_ids: Optional[List[int]] = []
|
|
|
|
class UserUpdate(BaseModel):
|
|
username: Optional[str] = None
|
|
password: Optional[str] = None
|
|
role: Optional[str] = None
|
|
franchise_id: Optional[int] = None
|
|
store_id: Optional[int] = None
|
|
is_active: Optional[bool] = None
|
|
managed_store_ids: Optional[List[int]] = None
|
|
|
|
class User(UserBase):
|
|
id: int
|
|
is_active: bool
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
managed_stores: List[Store] = []
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class UserLogin(BaseModel):
|
|
username: str
|
|
password: str
|
|
|
|
class Token(BaseModel):
|
|
access_token: str
|
|
token_type: str
|
|
|
|
class TokenData(BaseModel):
|
|
username: Optional[str] = None
|
|
|
|
# System Admin Response Schemas
|
|
class UserListResponse(User):
|
|
franchise_name: Optional[str] = None
|
|
store_name: Optional[str] = None
|
|
|
|
class StoreListResponse(Store):
|
|
franchise_name: Optional[str] = None
|
|
|
|
class MemberListResponse(Member):
|
|
franchise_name: Optional[str] = None
|
|
store_name: Optional[str] = None
|