diff --git a/backend/app/models/gift.py b/backend/app/models/gift.py new file mode 100644 index 0000000..7cf2f59 --- /dev/null +++ b/backend/app/models/gift.py @@ -0,0 +1,121 @@ +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"" \ No newline at end of file