Files
servesync-demo/backend/app/models.py
Nemo 4421f61c24 feat: Rebuild as Court Manager Dashboard v2
- Complete rewrite for single Court Manager user
- Feature 1: Player Management (CRUD, ELO, skill level)
- Feature 2: Court Reservation (timeline, override)
- Feature 3: Match Event Builder (3-step wizard)
  - Step 1: Event setup with player selection + stage calculator
  - Step 2: Stage configurator (Open/Skill/RR/Tournament)
  - Step 3: Auto bracket generation
- Feature 4: Live Court View (main dashboard, 2x2 grid)
- Event Control: score entry, bracket advancement, court assignment
- Screen view: TV display per court (/screen/:id)
- Seed: 8 players, 4 courts, 1 completed + 1 active event
- Routes: / players courts events events/new events/:id screen/:id
2026-03-19 07:35:51 +08:00

129 lines
5.5 KiB
Python

from datetime import datetime
from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, ForeignKey, JSON, Text
from sqlalchemy.orm import relationship
from .database import Base
class Player(Base):
__tablename__ = "players"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), nullable=False)
contact = Column(String(100), nullable=True)
skill_level = Column(String(20), nullable=False, default="Beginner") # Beginner/Intermediate/Advanced/Elite
matches_played = Column(Integer, default=0)
wins = Column(Integer, default=0)
losses = Column(Integer, default=0)
elo = Column(Float, default=1000.0)
created_at = Column(DateTime, default=datetime.utcnow)
event_players = relationship("EventPlayer", back_populates="player")
class Court(Base):
__tablename__ = "courts"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), nullable=False)
court_type = Column(String(100), default="Sport Court")
status = Column(String(20), default="available") # available/in_use/reserved/maintenance
created_at = Column(DateTime, default=datetime.utcnow)
reservations = relationship("CourtReservation", back_populates="court")
matches = relationship("Match", back_populates="court")
class CourtReservation(Base):
__tablename__ = "court_reservations"
id = Column(Integer, primary_key=True, index=True)
court_id = Column(Integer, ForeignKey("courts.id"), nullable=False)
player_id = Column(Integer, ForeignKey("players.id"), nullable=True)
group_name = Column(String(100), nullable=True)
start_time = Column(DateTime, nullable=False)
end_time = Column(DateTime, nullable=False)
notes = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
court = relationship("Court", back_populates="reservations")
player = relationship("Player")
class Event(Base):
__tablename__ = "events"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(200), nullable=False)
format = Column(String(20), default="Singles") # Singles/Doubles
courts_count = Column(Integer, default=1)
status = Column(String(20), default="setup") # setup/active/completed
current_stage_id = Column(Integer, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
started_at = Column(DateTime, nullable=True)
completed_at = Column(DateTime, nullable=True)
event_players = relationship("EventPlayer", back_populates="event", cascade="all, delete-orphan")
stages = relationship("Stage", back_populates="event", order_by="Stage.order", cascade="all, delete-orphan")
class EventPlayer(Base):
__tablename__ = "event_players"
id = Column(Integer, primary_key=True, index=True)
event_id = Column(Integer, ForeignKey("events.id"), nullable=False)
player_id = Column(Integer, ForeignKey("players.id"), nullable=False)
skill_level_override = Column(String(20), nullable=True)
event = relationship("Event", back_populates="event_players")
player = relationship("Player", back_populates="event_players")
class Stage(Base):
__tablename__ = "stages"
id = Column(Integer, primary_key=True, index=True)
event_id = Column(Integer, ForeignKey("events.id"), nullable=False)
name = Column(String(100), nullable=False)
match_type = Column(String(30), nullable=False) # Open/Skill-Based/Round Robin/Tournament
rounds = Column(Integer, default=1)
advance_count = Column(Integer, default=0) # 0 = all advance
status = Column(String(20), default="pending") # pending/active/completed
order = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.utcnow)
event = relationship("Event", back_populates="stages")
matches = relationship("Match", back_populates="stage", cascade="all, delete-orphan")
class Match(Base):
__tablename__ = "matches"
id = Column(Integer, primary_key=True, index=True)
stage_id = Column(Integer, ForeignKey("stages.id"), nullable=False)
court_id = Column(Integer, ForeignKey("courts.id"), nullable=True)
round_number = Column(Integer, default=1)
match_number = Column(Integer, default=1)
# Singles: player1_id, player2_id
# Doubles: team1_players and team2_players are JSON lists
player1_id = Column(Integer, ForeignKey("players.id"), nullable=True)
player2_id = Column(Integer, ForeignKey("players.id"), nullable=True)
team1_players = Column(JSON, nullable=True) # [player_id, player_id]
team2_players = Column(JSON, nullable=True)
score1 = Column(Integer, nullable=True)
score2 = Column(Integer, nullable=True)
winner_id = Column(Integer, ForeignKey("players.id"), nullable=True) # for singles
winner_team = Column(Integer, nullable=True) # 1 or 2 for doubles
status = Column(String(20), default="scheduled") # scheduled/in_progress/completed/bye
bracket_position = Column(String(20), nullable=True) # winners/losers
next_winner_match_id = Column(Integer, nullable=True)
next_loser_match_id = Column(Integer, nullable=True)
scheduled_time = Column(DateTime, nullable=True)
started_at = Column(DateTime, nullable=True)
completed_at = Column(DateTime, nullable=True)
stage = relationship("Stage", back_populates="matches")
court = relationship("Court", back_populates="matches")
player1 = relationship("Player", foreign_keys=[player1_id])
player2 = relationship("Player", foreign_keys=[player2_id])
winner = relationship("Player", foreign_keys=[winner_id])