Files
org-parking/database/models.py
Stefano Manfredi 7168fa4b72 Refactor to manager-centric model, add team calendar for all users
Key changes:
- Removed office-centric model (deleted offices.py, office-rules)
- Renamed to team-rules, managers are part of their own team
- Team calendar visible to all (read-only for employees)
- Admins can have a manager assigned
2025-12-02 13:30:04 +00:00

195 lines
7.4 KiB
Python

"""
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'),
)