197 lines
6.7 KiB
Python
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]
|