Primo commit
This commit is contained in:
@@ -2,12 +2,73 @@
|
||||
SQLAlchemy ORM Models
|
||||
Clean, focused data models for parking management
|
||||
"""
|
||||
from sqlalchemy import Column, String, Integer, Text, ForeignKey, Index
|
||||
import enum
|
||||
from sqlalchemy import Column, String, Integer, Text, ForeignKey, Index, Enum, Date, DateTime, Boolean
|
||||
from sqlalchemy.orm import relationship, declarative_base
|
||||
from datetime import datetime, date
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class UserRole(str, enum.Enum):
|
||||
ADMIN = "admin"
|
||||
MANAGER = "manager"
|
||||
EMPLOYEE = "employee"
|
||||
|
||||
|
||||
class PresenceStatus(str, enum.Enum):
|
||||
PRESENT = "present"
|
||||
REMOTE = "remote"
|
||||
ABSENT = "absent"
|
||||
|
||||
|
||||
class NotificationType(str, enum.Enum):
|
||||
PRESENCE_REMINDER = "presence_reminder"
|
||||
WEEKLY_PARKING = "weekly_parking"
|
||||
DAILY_PARKING = "daily_parking"
|
||||
PARKING_CHANGE = "parking_change"
|
||||
|
||||
|
||||
class WeekDay(enum.IntEnum):
|
||||
# Matches Python's calendar (0=Monday)? No!
|
||||
# The current DB convention in ManagerWeeklyClosingDay seems to be 0=Sunday based on comment:
|
||||
# "0=Sunday, 1=Monday, ..., 6=Saturday"
|
||||
# To keep consistency with existing logic comments, we'll stick to that,
|
||||
# OR we can switch to standard Python (0=Monday).
|
||||
# Plan said: "IntEnum matching DB convention (0=Sunday, 1=Monday, ...)"
|
||||
MONDAY = 0
|
||||
TUESDAY = 1
|
||||
WEDNESDAY = 2
|
||||
THURSDAY = 3
|
||||
FRIDAY = 4
|
||||
SATURDAY = 5
|
||||
SUNDAY = 6
|
||||
|
||||
|
||||
|
||||
class Office(Base):
|
||||
"""Organization units that have parking spots"""
|
||||
__tablename__ = "offices"
|
||||
|
||||
id = Column(Text, primary_key=True)
|
||||
name = Column(Text, nullable=False)
|
||||
parking_quota = Column(Integer, default=0)
|
||||
spot_prefix = Column(Text) # Letter prefix: A, B, C
|
||||
|
||||
# Booking Window Settings (Batch Assignment)
|
||||
booking_window_enabled = Column(Boolean, default=False)
|
||||
booking_window_end_hour = Column(Integer, default=18) # 0-23
|
||||
booking_window_end_minute = Column(Integer, default=0) # 0-59
|
||||
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
users = relationship("User", back_populates="office")
|
||||
closing_days = relationship("OfficeClosingDay", back_populates="office", cascade="all, delete-orphan")
|
||||
weekly_closing_days = relationship("OfficeWeeklyClosingDay", back_populates="office", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""Application users"""
|
||||
__tablename__ = "users"
|
||||
@@ -16,34 +77,30 @@ class User(Base):
|
||||
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.
|
||||
role = Column(Enum(UserRole, values_callable=lambda obj: [e.value for e in obj]), nullable=False, default=UserRole.EMPLOYEE)
|
||||
office_id = Column(Text, ForeignKey("offices.id")) # Which office this user belongs to
|
||||
|
||||
# User preferences
|
||||
week_start_day = Column(Integer, default=0) # 0=Sunday, 1=Monday, ..., 6=Saturday
|
||||
week_start_day = Column(Integer, default=0) # 0=Sunday, 1=Monday, ... (Matches WeekDay logic)
|
||||
|
||||
# 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_weekly_parking = Column(Boolean, default=True) # Weekly parking summary (Friday at 12)
|
||||
notify_daily_parking = Column(Boolean, default=True) # 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
|
||||
notify_parking_changes = Column(Boolean, default=True) # Immediate notification on assignment changes
|
||||
|
||||
created_at = Column(Text)
|
||||
updated_at = Column(Text)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
manager = relationship("User", remote_side=[id], backref="managed_users")
|
||||
office = relationship("Office", back_populates="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'),
|
||||
Index('idx_user_office', 'office_id'),
|
||||
)
|
||||
|
||||
|
||||
@@ -53,10 +110,10 @@ class UserPresence(Base):
|
||||
|
||||
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)
|
||||
date = Column(Date, nullable=False)
|
||||
status = Column(Enum(PresenceStatus, values_callable=lambda obj: [e.value for e in obj]), nullable=False) # present, remote, absent
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
user = relationship("User", back_populates="presences")
|
||||
@@ -68,97 +125,100 @@ class UserPresence(Base):
|
||||
|
||||
|
||||
class DailyParkingAssignment(Base):
|
||||
"""Parking spot assignments per day - spots belong to managers"""
|
||||
"""Parking spot assignments per day - spots belong to offices"""
|
||||
__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)
|
||||
date = Column(Date, nullable=False)
|
||||
spot_id = Column(Text, nullable=False) # A1, A2, B1, B2, etc. (prefix from office)
|
||||
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)
|
||||
office_id = Column(Text, ForeignKey("offices.id", ondelete="CASCADE"), nullable=False) # Office that owns the spot
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
user = relationship("User", back_populates="assignments", foreign_keys=[user_id])
|
||||
manager = relationship("User", foreign_keys=[manager_id])
|
||||
office = relationship("Office")
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_assignment_manager_date', 'manager_id', 'date'),
|
||||
Index('idx_assignment_office_date', 'office_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"
|
||||
class OfficeClosingDay(Base):
|
||||
"""Specific date closing days for an office's parking pool (holidays, special closures)"""
|
||||
__tablename__ = "office_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
|
||||
office_id = Column(Text, ForeignKey("offices.id", ondelete="CASCADE"), nullable=False)
|
||||
date = Column(Date, nullable=False)
|
||||
end_date = Column(Date)
|
||||
reason = Column(Text)
|
||||
|
||||
# Relationships
|
||||
manager = relationship("User")
|
||||
office = relationship("Office", back_populates="closing_days")
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_closing_manager_date', 'manager_id', 'date', unique=True),
|
||||
Index('idx_closing_office_date', 'office_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"
|
||||
class OfficeWeeklyClosingDay(Base):
|
||||
"""Weekly recurring closing days for an office's parking pool (e.g., Saturday and Sunday)"""
|
||||
__tablename__ = "office_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
|
||||
office_id = Column(Text, ForeignKey("offices.id", ondelete="CASCADE"), nullable=False)
|
||||
weekday = Column(Integer, nullable=False) # 0=Sunday, 1=Monday, ..., 6=Saturday (Matches WeekDay Enum logic)
|
||||
|
||||
# Relationships
|
||||
manager = relationship("User")
|
||||
office = relationship("Office", back_populates="weekly_closing_days")
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_weekly_closing_manager_day', 'manager_id', 'weekday', unique=True),
|
||||
Index('idx_weekly_closing_office_day', 'office_id', 'weekday', unique=True),
|
||||
)
|
||||
|
||||
|
||||
class ParkingGuarantee(Base):
|
||||
"""Users guaranteed a parking spot when present (set by manager)"""
|
||||
"""Users guaranteed a parking spot when present (set by office manager)"""
|
||||
__tablename__ = "parking_guarantees"
|
||||
|
||||
id = Column(Text, primary_key=True)
|
||||
manager_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
||||
office_id = Column(Text, ForeignKey("offices.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)
|
||||
start_date = Column(Date) # Optional (null = no start limit)
|
||||
end_date = Column(Date) # Optional (null = no end limit)
|
||||
notes = Column(Text, nullable=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
manager = relationship("User", foreign_keys=[manager_id])
|
||||
office = relationship("Office")
|
||||
user = relationship("User", foreign_keys=[user_id])
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_guarantee_manager_user', 'manager_id', 'user_id', unique=True),
|
||||
Index('idx_guarantee_office_user', 'office_id', 'user_id', unique=True),
|
||||
)
|
||||
|
||||
|
||||
class ParkingExclusion(Base):
|
||||
"""Users excluded from parking assignment (set by manager)"""
|
||||
"""Users excluded from parking assignment (set by office manager)"""
|
||||
__tablename__ = "parking_exclusions"
|
||||
|
||||
id = Column(Text, primary_key=True)
|
||||
manager_id = Column(Text, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
||||
office_id = Column(Text, ForeignKey("offices.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)
|
||||
start_date = Column(Date) # Optional
|
||||
end_date = Column(Date) # Optional
|
||||
notes = Column(Text, nullable=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
manager = relationship("User", foreign_keys=[manager_id])
|
||||
office = relationship("Office")
|
||||
user = relationship("User", foreign_keys=[user_id])
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_exclusion_manager_user', 'manager_id', 'user_id', unique=True),
|
||||
Index('idx_exclusion_office_user', 'office_id', 'user_id', unique=True),
|
||||
)
|
||||
|
||||
|
||||
@@ -168,9 +228,9 @@ class NotificationLog(Base):
|
||||
|
||||
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)
|
||||
notification_type = Column(Enum(NotificationType, values_callable=lambda obj: [e.value for e in obj]), nullable=False)
|
||||
reference_date = Column(Text) # The date/week this notification refers to (YYYY-MM-DD or YYYY-Www) - keeping as Text for flexibility
|
||||
sent_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_notification_user_type_date', 'user_id', 'notification_type', 'reference_date'),
|
||||
@@ -183,11 +243,11 @@ class NotificationQueue(Base):
|
||||
|
||||
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
|
||||
notification_type = Column(Enum(NotificationType, values_callable=lambda obj: [e.value for e in obj]), 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
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
sent_at = Column(DateTime) # null = not sent yet
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_queue_pending', 'sent_at'),
|
||||
|
||||
Reference in New Issue
Block a user