Files
eventspace/backend/app/models/gift.py
Felipe Cardoso 4ef202cc5a
All checks were successful
Build and Push Docker Images / changes (push) Successful in 5s
Build and Push Docker Images / build-backend (push) Successful in 52s
Build and Push Docker Images / build-frontend (push) Has been skipped
Big refactor of gift categories model
2025-03-16 14:51:04 +01:00

197 lines
6.7 KiB
Python

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"<GiftItem {self.name} ({self.status.value})>"
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"<GiftPurchase gift_id={self.gift_id} guest_id={self.guest_id}>"
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"<EventGiftCategory event_id={self.event_id} category_id={self.category_id}>"
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"<GiftCategory {self.name}>"
@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]