Files
home_hub/backend/app/models/shopping.py
T
gilles aa9ac2a6ea feat(shopping): tags sur les articles du catalogue
- Migration 006 : colonne tags TEXT[] sur shopping.products
- Modèle SQLAlchemy + schémas Pydantic mis à jour (ProductCreate/Update/Response)
- Interface TypeScript Product/ProductCreate/ProductUpdate avec tags?: string[]
- CatalogueModal : chip input (Entrée/virgule pour ajouter, clic pour supprimer, Backspace pour retirer le dernier)
- Recherche dans le catalogue et le bottom sheet étendue aux tags (insensible aux accents)
- Tags affichés en pills dans la liste du catalogue

v0.4.12

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 12:57:25 +02:00

92 lines
5.0 KiB
Python

import uuid
from datetime import datetime, date
from decimal import Decimal
from sqlalchemy import String, Text, Integer, TIMESTAMP, Date, Numeric, Boolean, ForeignKey, text
from sqlalchemy.dialects.postgresql import UUID, ARRAY
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
class Product(Base):
__tablename__ = "products"
__table_args__ = {"schema": "shopping"}
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name: Mapped[str] = mapped_column(String(150), nullable=False)
brand: Mapped[str | None] = mapped_column(String(100))
category: Mapped[str | None] = mapped_column(String(50))
description: Mapped[str | None] = mapped_column(Text)
image_path: Mapped[str | None] = mapped_column(String(255))
thumbnail_path: Mapped[str | None] = mapped_column(String(255))
default_unit: Mapped[str | None] = mapped_column(String(20))
barcode: Mapped[str | None] = mapped_column(String(50))
price: Mapped[Decimal | None] = mapped_column(Numeric(8, 2))
quantity_per_unit: Mapped[Decimal | None] = mapped_column(Numeric(8, 3))
default_store_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("shopping.stores.id", ondelete="SET NULL"))
frequency_score: Mapped[int] = mapped_column(Integer, server_default=text("0"))
last_purchased_at: Mapped[date | None] = mapped_column(Date)
avg_interval_days: Mapped[Decimal | None] = mapped_column(Numeric(8, 1))
tags: Mapped[list[str] | None] = mapped_column(ARRAY(Text()), nullable=True)
owner_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))
created_at: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), server_default=text("now()"))
class Store(Base):
__tablename__ = "stores"
__table_args__ = {"schema": "shopping"}
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name: Mapped[str] = mapped_column(String(100), nullable=False)
location: Mapped[str | None] = mapped_column(Text)
url: Mapped[str | None] = mapped_column(Text)
store_type: Mapped[str | None] = mapped_column(String(50))
owner_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))
class PriceHistory(Base):
__tablename__ = "price_history"
__table_args__ = {"schema": "shopping"}
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
product_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("shopping.products.id", ondelete="CASCADE"), nullable=False)
store_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("shopping.stores.id", ondelete="SET NULL"))
price: Mapped[Decimal] = mapped_column(Numeric(8, 2), nullable=False)
unit: Mapped[str | None] = mapped_column(String(20))
quantity: Mapped[Decimal | None] = mapped_column(Numeric(8, 3))
source: Mapped[str] = mapped_column(String(20), server_default="manual")
recorded_at: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), server_default=text("now()"))
class ShoppingList(Base):
__tablename__ = "lists"
__table_args__ = {"schema": "shopping"}
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name: Mapped[str | None] = mapped_column(String(100))
store_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("shopping.stores.id", ondelete="SET NULL"))
week_date: Mapped[date | None] = mapped_column(Date)
status: Mapped[str] = mapped_column(String(20), server_default="draft")
owner_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True))
created_at: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=True), server_default=text("now()"))
items: Mapped[list["ListItem"]] = relationship("ListItem", back_populates="shopping_list", cascade="all, delete-orphan")
class ListItem(Base):
__tablename__ = "list_items"
__table_args__ = {"schema": "shopping"}
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
list_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("shopping.lists.id", ondelete="CASCADE"), nullable=False)
product_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("shopping.products.id", ondelete="SET NULL"))
custom_name: Mapped[str | None] = mapped_column(String(150))
quantity: Mapped[Decimal | None] = mapped_column(Numeric(8, 3))
unit: Mapped[str | None] = mapped_column(String(20))
is_checked: Mapped[bool] = mapped_column(Boolean, server_default=text("false"))
price_recorded: Mapped[Decimal | None] = mapped_column(Numeric(8, 2))
carried_over: Mapped[bool] = mapped_column(Boolean, server_default=text("false"))
sort_order: Mapped[int | None] = mapped_column(Integer)
shopping_list: Mapped["ShoppingList"] = relationship("ShoppingList", back_populates="items")
product: Mapped["Product | None"] = relationship("Product", lazy="select")