""" SQLAlchemy ORM Models Clean, focused data models for parking management """ from sqlalchemy import Column, String, Integer, Text, ForeignKey, Index from sqlalchemy.orm import relationship, declarative_base Base = declarative_base() class User(Base): """Application users""" __tablename__ = "users" id = Column(Text, primary_key=True) email = Column(Text, unique=True, nullable=False) password_hash = Column(Text) name = Column(Text) role = Column(Text, nullable=False, default="employee") # admin, manager, employee manager_id = Column(Text, ForeignKey("users.id")) # Who manages this user (any user can have a manager) # Manager-specific fields (only relevant for role='manager') manager_parking_quota = Column(Integer, default=0) # Number of spots this manager controls manager_spot_prefix = Column(Text) # Letter prefix for spots: A, B, C, etc. # User preferences week_start_day = Column(Integer, default=0) # 0=Sunday, 1=Monday, ..., 6=Saturday # Notification preferences notify_weekly_parking = Column(Integer, default=1) # Weekly parking summary (Friday at 12) notify_daily_parking = Column(Integer, default=1) # Daily parking reminder notify_daily_parking_hour = Column(Integer, default=8) # Hour for daily reminder (0-23) notify_daily_parking_minute = Column(Integer, default=0) # Minute for daily reminder (0-59) notify_parking_changes = Column(Integer, default=1) # Immediate notification on assignment changes created_at = Column(Text) updated_at = Column(Text) # Relationships manager = relationship("User", remote_side=[id], backref="managed_users") presences = relationship("UserPresence", back_populates="user", cascade="all, delete-orphan") assignments = relationship("DailyParkingAssignment", back_populates="user", foreign_keys="DailyParkingAssignment.user_id") __table_args__ = ( Index('idx_user_email', 'email'), Index('idx_user_manager', 'manager_id'), ) class UserPresence(Base): """Daily presence records""" __tablename__ = "user_presences" id = Column(Text, primary_key=True) user_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) date = Column(Text, nullable=False) # YYYY-MM-DD status = Column(Text, nullable=False) # present, remote, absent created_at = Column(Text) updated_at = Column(Text) # Relationships user = relationship("User", back_populates="presences") __table_args__ = ( Index('idx_presence_user_date', 'user_id', 'date', unique=True), Index('idx_presence_date', 'date'), ) class DailyParkingAssignment(Base): """Parking spot assignments per day - spots belong to managers""" __tablename__ = "daily_parking_assignments" id = Column(Text, primary_key=True) date = Column(Text, nullable=False) # YYYY-MM-DD spot_id = Column(Text, nullable=False) # A1, A2, B1, B2, etc. (prefix from manager) user_id = Column(Text, ForeignKey("users.id", ondelete="SET NULL")) manager_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) # Manager who owns the spot created_at = Column(Text) # Relationships user = relationship("User", back_populates="assignments", foreign_keys=[user_id]) manager = relationship("User", foreign_keys=[manager_id]) __table_args__ = ( Index('idx_assignment_manager_date', 'manager_id', 'date'), Index('idx_assignment_user', 'user_id'), Index('idx_assignment_date_spot', 'date', 'spot_id'), ) class ManagerClosingDay(Base): """Specific date closing days for a manager's parking pool (holidays, special closures)""" __tablename__ = "manager_closing_days" id = Column(Text, primary_key=True) manager_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) date = Column(Text, nullable=False) # YYYY-MM-DD reason = Column(Text) # Relationships manager = relationship("User") __table_args__ = ( Index('idx_closing_manager_date', 'manager_id', 'date', unique=True), ) class ManagerWeeklyClosingDay(Base): """Weekly recurring closing days for a manager's parking pool (e.g., Saturday and Sunday)""" __tablename__ = "manager_weekly_closing_days" id = Column(Text, primary_key=True) manager_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) weekday = Column(Integer, nullable=False) # 0=Sunday, 1=Monday, ..., 6=Saturday # Relationships manager = relationship("User") __table_args__ = ( Index('idx_weekly_closing_manager_day', 'manager_id', 'weekday', unique=True), ) class ParkingGuarantee(Base): """Users guaranteed a parking spot when present (set by manager)""" __tablename__ = "parking_guarantees" id = Column(Text, primary_key=True) manager_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) user_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) start_date = Column(Text) # Optional: YYYY-MM-DD (null = no start limit) end_date = Column(Text) # Optional: YYYY-MM-DD (null = no end limit) created_at = Column(Text) # Relationships manager = relationship("User", foreign_keys=[manager_id]) user = relationship("User", foreign_keys=[user_id]) __table_args__ = ( Index('idx_guarantee_manager_user', 'manager_id', 'user_id', unique=True), ) class ParkingExclusion(Base): """Users excluded from parking assignment (set by manager)""" __tablename__ = "parking_exclusions" id = Column(Text, primary_key=True) manager_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) user_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) start_date = Column(Text) # Optional: YYYY-MM-DD (null = no start limit) end_date = Column(Text) # Optional: YYYY-MM-DD (null = no end limit) created_at = Column(Text) # Relationships manager = relationship("User", foreign_keys=[manager_id]) user = relationship("User", foreign_keys=[user_id]) __table_args__ = ( Index('idx_exclusion_manager_user', 'manager_id', 'user_id', unique=True), ) class NotificationLog(Base): """Log of sent notifications to prevent duplicates""" __tablename__ = "notification_logs" id = Column(Text, primary_key=True) user_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) notification_type = Column(Text, nullable=False) # presence_reminder, weekly_parking, daily_parking, parking_change reference_date = Column(Text) # The date/week this notification refers to (YYYY-MM-DD or YYYY-Www) sent_at = Column(Text, nullable=False) __table_args__ = ( Index('idx_notification_user_type_date', 'user_id', 'notification_type', 'reference_date'), ) class NotificationQueue(Base): """Queue for pending notifications (for immediate parking change notifications)""" __tablename__ = "notification_queue" id = Column(Text, primary_key=True) user_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False) notification_type = Column(Text, nullable=False) # parking_change subject = Column(Text, nullable=False) body = Column(Text, nullable=False) created_at = Column(Text, nullable=False) sent_at = Column(Text) # null = not sent yet __table_args__ = ( Index('idx_queue_pending', 'sent_at'), )