from datetime import datetime, timezone from enum import Enum from sqlalchemy import Column, String, Boolean, ForeignKey, UniqueConstraint, Integer, Enum as SQLEnum, DateTime, Table from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.orm import relationship from .base import Base, TimestampMixin, UUIDMixin class GuestStatus(str, Enum): INVITED = "invited" # Initial state when guest is added PENDING = "pending" # Invitation sent, waiting for response CONFIRMED = "confirmed" # RSVP confirmed attendance DECLINED = "declined" # RSVP declined attendance WAITLISTED = "waitlisted" # On waitlist if event is at capacity CANCELLED = "cancelled" # Guest cancelled after initial confirmation class Guest(Base, UUIDMixin, TimestampMixin): __tablename__ = 'guests' # Foreign Keys event_id = Column(UUID(as_uuid=True), ForeignKey('events.id'), nullable=False) invited_by = Column(UUID(as_uuid=True), ForeignKey('users.id'), nullable=False) user_id = Column(UUID(as_uuid=True), ForeignKey('users.id')) # Optional, if guest is a registered user # Guest Information full_name = Column(String, nullable=False) email = Column(String) phone = Column(String) invitation_code = Column(String, unique=True, nullable=False) # Guest Status status = Column(SQLEnum(GuestStatus), nullable=False, default=GuestStatus.INVITED) max_additional_guests = Column(Integer, default=0) actual_additional_guests = Column(Integer, default=0) # Tracking invitation_sent_at = Column(DateTime(timezone=True)) last_reminded_at = Column(DateTime(timezone=True)) response_date = Column(DateTime(timezone=True)) # Additional Information dietary_restrictions = Column(String) notes = Column(String) custom_fields = Column(JSONB) # Access Management is_blocked = Column(Boolean, default=False) can_bring_guests = Column(Boolean, default=False) # Relationships event = relationship("Event", back_populates="guests") inviter = relationship("User", foreign_keys=[invited_by]) user = relationship("User", foreign_keys=[user_id]) rsvp = relationship("RSVP", back_populates="guest", uselist=False) gifts = relationship("GiftItem", secondary="guest_gifts", back_populates="reserved_by") __table_args__ = ( UniqueConstraint('event_id', 'email', name='uq_event_guest_email'), ) def __repr__(self): return f"" @property def total_guests(self): """Total number of guests including additional guests""" return 1 + (self.actual_additional_guests or 0) @property def has_responded(self): """Check if guest has responded to invitation""" return self.status in (GuestStatus.CONFIRMED, GuestStatus.DECLINED) def update_status(self, new_status: GuestStatus): """Update guest status and record response date""" self.status = new_status if new_status in (GuestStatus.CONFIRMED, GuestStatus.DECLINED): self.response_date = datetime.now(timezone.utc) # Association table for guest gifts (many-to-many relationship) guest_gifts = Table( 'guest_gifts', Base.metadata, Column('guest_id', UUID(as_uuid=True), ForeignKey('guests.id'), primary_key=True), Column('gift_id', UUID(as_uuid=True), ForeignKey('gift_items.id'), primary_key=True), Column('reserved_at', DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)), Column('notes', String), )