Files
waiting-system/schemas.py
Jun-dev f699a29a85 Add waiting system application files
- 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>
2025-12-14 00:29:39 +09:00

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