from datetime import datetime, timezone from enum import Enum from sqlalchemy import ( Column, String, Boolean, ForeignKey, UniqueConstraint, Integer, DateTime, Enum as SQLEnum, Table, Float ) from sqlalchemy.orm import relationship from sqlalchemy.dialects.postgresql import UUID, JSONB from .base import Base, TimestampMixin, UUIDMixin class GiftStatus(str, Enum): AVAILABLE = "available" RESERVED = "reserved" PURCHASED = "purchased" RECEIVED = "received" REMOVED = "removed" class GiftPriority(str, Enum): LOW = "low" MEDIUM = "medium" HIGH = "high" MUST_HAVE = "must_have" class GiftItem(Base, UUIDMixin, TimestampMixin): __tablename__ = 'gift_items' # Foreign Keys event_id = Column(UUID(as_uuid=True), ForeignKey('events.id'), nullable=False) added_by = Column(UUID(as_uuid=True), ForeignKey('users.id'), nullable=False) category_id = Column(UUID(as_uuid=True), ForeignKey('gift_categories.id')) # Gift Details name = Column(String, nullable=False) description = Column(String) price = Column(Float) currency = Column(String, default='USD') quantity_requested = Column(Integer, default=1) quantity_received = Column(Integer, default=0) # Status and Priority status = Column(SQLEnum(GiftStatus), nullable=False, default=GiftStatus.AVAILABLE) priority = Column(SQLEnum(GiftPriority), default=GiftPriority.MEDIUM) # Purchase Information purchase_url = Column(String) store_name = Column(String) brand = Column(String) model = Column(String) # Display image_url = Column(String) display_order = Column(Integer) is_visible = Column(Boolean, default=True) # Additional Information notes = Column(String) custom_fields = Column(JSONB) # Tracking last_status_change = Column(DateTime(timezone=True)) # Relationships event = relationship("Event", back_populates="gifts") added_by_user = relationship("User", foreign_keys=[added_by]) category = relationship("GiftCategory", back_populates="gifts") reserved_by = relationship("Guest", secondary="guest_gifts", back_populates="gifts") purchase_history = relationship("GiftPurchase", back_populates="gift_item", order_by="desc(GiftPurchase.purchased_at)") def __repr__(self): return f"" def update_status(self, new_status: GiftStatus): """Update gift status and record the change time""" self.status = new_status self.last_status_change = datetime.now(timezone.utc) @property def remaining_quantity(self) -> int: """Calculate remaining quantity needed""" return max(0, self.quantity_requested - self.quantity_received) @property def is_fully_received(self) -> bool: """Check if all requested quantities have been received""" return self.quantity_received >= self.quantity_requested @property def formatted_price(self) -> str: """Return formatted price with currency""" if self.price is None: return "Price not set" return f"{self.price:.2f} {self.currency}" # Association table for tracking gift purchases class GiftPurchase(Base, UUIDMixin): __tablename__ = 'gift_purchases' gift_id = Column(UUID(as_uuid=True), ForeignKey('gift_items.id'), nullable=False) guest_id = Column(UUID(as_uuid=True), ForeignKey('guests.id'), nullable=False) quantity = Column(Integer, default=1, nullable=False) purchased_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False) purchase_price = Column(Float) purchase_currency = Column(String) notes = Column(String) # Relationships gift_item = relationship("GiftItem", back_populates="purchase_history") guest = relationship("Guest") def __repr__(self): return f"" class EventGiftCategory(Base): __tablename__ = 'event_gift_categories' event_id = Column(UUID(as_uuid=True), ForeignKey('events.id'), primary_key=True) category_id = Column(UUID(as_uuid=True), ForeignKey('gift_categories.id'), primary_key=True) display_order = Column(Integer, default=0) is_visible = Column(Boolean, default=True) created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False) # Relationships event = relationship("Event", back_populates="category_associations") category = relationship("GiftCategory", back_populates="event_associations") def __repr__(self): return f"" class GiftCategory(Base, UUIDMixin, TimestampMixin): __tablename__ = 'gift_categories' # Foreign Keys created_by = Column(UUID(as_uuid=True), ForeignKey('users.id'), nullable=False) # Category Details name = Column(String, nullable=False, unique=True) description = Column(String) # Display icon = Column(String) # Icon identifier or URL color = Column(String) # Color code (hex/rgb) # Additional Settings custom_fields = Column(JSONB) # Relationships event_associations = relationship("EventGiftCategory", back_populates="category") gifts = relationship("GiftItem", back_populates="category", order_by="GiftItem.display_order") created_by_user = relationship("User", foreign_keys=[created_by]) def __repr__(self): return f"" @property def total_gifts(self) -> int: """Get the total quantity of gifts requested in the category.""" return sum(gift.quantity_requested for gift in self.gifts) @property def available_gifts(self) -> int: """Get number of available gifts in category""" return sum(1 for gift in self.gifts if gift.status == 'available' and gift.is_visible) def reorder_gifts(self, gift_ids: list[UUID]) -> None: """ Reorder gifts within the category Args: gift_ids: List of gift IDs in desired order """ # Validate gift IDs existing_gift_ids = {gift.id for gift in self.gifts} for gift_id in gift_ids: if gift_id not in existing_gift_ids: raise ValueError(f"Gift ID {gift_id} not found in category") # Reorder logic gift_order = {gift_id: idx for idx, gift_id in enumerate(gift_ids)} for gift in self.gifts: if gift.id in gift_order: gift.display_order = gift_order[gift.id]