Files
eventspace/backend/app/models/gift.py
Felipe Cardoso 38d5ac7c63 Add unit tests for GiftItem, GiftCategory, and GiftPurchase models
Introduced comprehensive test coverage for GiftItem, GiftCategory, and GiftPurchase models to validate core functionality such as creation, updates, deletions, and derived properties. Also updated the `total_gifts` method in `GiftCategory` to calculate the total requested quantity rather than the count of gifts.
2025-02-28 14:41:43 +01:00

181 lines
6.0 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 GiftCategory(Base, UUIDMixin, TimestampMixin):
__tablename__ = 'gift_categories'
# Foreign Keys
event_id = Column(UUID(as_uuid=True), ForeignKey('events.id'), nullable=False)
created_by = Column(UUID(as_uuid=True), ForeignKey('users.id'), nullable=False)
# Category Details
name = Column(String, nullable=False)
description = Column(String)
# Display
icon = Column(String) # Icon identifier or URL
color = Column(String) # Color code (hex/rgb)
display_order = Column(Integer, default=0)
is_visible = Column(Boolean, default=True)
# Additional Settings
custom_fields = Column(JSONB)
# Relationships
event = relationship("Event", back_populates="gift_categories")
gifts = relationship("GiftItem",
back_populates="category",
order_by="GiftItem.display_order")
created_by_user = relationship("User", foreign_keys=[created_by])
# Ensure unique category names within an event
__table_args__ = (
UniqueConstraint('event_id', 'name', name='uq_event_category_name'),
)
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
"""
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]