# models/database/cart.py """Cart item database model. Money values are stored as integer cents (e.g., €105.91 = 10591). See docs/architecture/money-handling.md for details. """ from sqlalchemy import ( Column, ForeignKey, Index, Integer, String, UniqueConstraint, ) from sqlalchemy.orm import relationship from app.core.database import Base from app.utils.money import cents_to_euros, euros_to_cents from models.database.base import TimestampMixin class CartItem(Base, TimestampMixin): """ Shopping cart items. Stores cart items per session, vendor, and product. Sessions are identified by a session_id string (from browser cookies). Price is stored as integer cents for precision. """ __tablename__ = "cart_items" id = Column(Integer, primary_key=True, index=True) vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False) product_id = Column(Integer, ForeignKey("products.id"), nullable=False) session_id = Column(String(255), nullable=False, index=True) # Cart details quantity = Column(Integer, nullable=False, default=1) price_at_add_cents = Column(Integer, nullable=False) # Price in cents when added # Relationships vendor = relationship("Vendor") product = relationship("Product") # Constraints __table_args__ = ( UniqueConstraint("vendor_id", "session_id", "product_id", name="uq_cart_item"), Index("idx_cart_session", "vendor_id", "session_id"), Index("idx_cart_created", "created_at"), # For cleanup of old carts ) def __repr__(self): return f"" # === PRICE PROPERTIES (Euro convenience accessors) === @property def price_at_add(self) -> float: """Get price at add in euros.""" return cents_to_euros(self.price_at_add_cents) @price_at_add.setter def price_at_add(self, value: float): """Set price at add from euros.""" self.price_at_add_cents = euros_to_cents(value) @property def line_total_cents(self) -> int: """Calculate line total in cents.""" return self.price_at_add_cents * self.quantity @property def line_total(self) -> float: """Calculate line total in euros.""" return cents_to_euros(self.line_total_cents)