from pydantic import BaseModel, Field from typing import Optional, List from datetime import datetime, date # Brand schemas class BrandBase(BaseModel): name: str class BrandCreate(BrandBase): pass class BrandUpdate(BaseModel): name: Optional[str] = None class Brand(BrandBase): id: int created_at: datetime updated_at: Optional[datetime] = None class Config: from_attributes = True # BrandInShop schemas class BrandInShopBase(BaseModel): shop_id: int brand_id: int class BrandInShopCreate(BrandInShopBase): pass class BrandInShopUpdate(BaseModel): shop_id: Optional[int] = None brand_id: Optional[int] = None class BrandInShop(BrandInShopBase): id: int created_at: datetime updated_at: Optional[datetime] = None shop: "Shop" brand: "Brand" class Config: from_attributes = True # Grocery Category schemas class GroceryCategoryBase(BaseModel): name: str class GroceryCategoryCreate(GroceryCategoryBase): pass class GroceryCategoryUpdate(BaseModel): name: Optional[str] = None class GroceryCategory(GroceryCategoryBase): id: int created_at: datetime updated_at: Optional[datetime] = None class Config: from_attributes = True # Product schemas class ProductBase(BaseModel): name: str category_id: int brand_id: Optional[int] = None organic: bool = False weight: Optional[float] = None weight_unit: str = "g" class ProductCreate(ProductBase): valid_from: Optional[date] = None # If not provided, will use current date class ProductUpdate(BaseModel): name: Optional[str] = None category_id: Optional[int] = None brand_id: Optional[int] = None organic: Optional[bool] = None weight: Optional[float] = None weight_unit: Optional[str] = None valid_from: Optional[date] = None # If not provided, will use current date class Product(ProductBase): id: int created_at: datetime updated_at: Optional[datetime] = None category: GroceryCategory brand: Optional[Brand] = None class Config: from_attributes = True # Historical Product schemas class ProductHistory(BaseModel): history_id: int id: int # Original product ID name: str category_id: int brand_id: Optional[int] = None organic: bool = False weight: Optional[float] = None weight_unit: str = "g" created_at: Optional[datetime] = None updated_at: Optional[datetime] = None valid_from: date valid_to: date deleted: bool operation: str # 'U' for Update, 'D' for Delete archived_at: datetime class Config: from_attributes = True class ProductAtDate(BaseModel): id: int name: str category_id: int category: GroceryCategory brand_id: Optional[int] = None brand: Optional[Brand] = None organic: bool = False weight: Optional[float] = None weight_unit: str = "g" valid_from: date valid_to: date deleted: bool was_current: bool # True if from current table, False if from history class Config: from_attributes = True class ProductAtPurchase(BaseModel): product: ProductAtDate amount: float price: float discount: bool class Config: from_attributes = True # Shop schemas class ShopBase(BaseModel): name: str city: str address: Optional[str] = None class ShopCreate(ShopBase): pass class ShopUpdate(BaseModel): name: Optional[str] = None city: Optional[str] = None address: Optional[str] = None class Shop(ShopBase): id: int created_at: datetime updated_at: Optional[datetime] = None class Config: from_attributes = True # Shopping Event schemas class ProductInEvent(BaseModel): product_id: int amount: float = Field(..., gt=0) price: float = Field(..., ge=0) # Price at the time of this shopping event (allow free items) discount: bool = False # Whether this product was purchased with a discount class ProductWithEventData(BaseModel): id: int name: str category: GroceryCategory brand: Optional[Brand] = None organic: bool weight: Optional[float] = None weight_unit: str amount: float # Amount purchased in this event price: float # Price at the time of this event discount: bool # Whether this product was purchased with a discount class Config: from_attributes = True class ShoppingEventBase(BaseModel): shop_id: int date: Optional[datetime] = None total_amount: Optional[float] = Field(None, ge=0) notes: Optional[str] = None class ShoppingEventCreate(ShoppingEventBase): products: List[ProductInEvent] = [] class ShoppingEventUpdate(BaseModel): shop_id: Optional[int] = None date: Optional[datetime] = None total_amount: Optional[float] = Field(None, ge=0) notes: Optional[str] = None products: Optional[List[ProductInEvent]] = None class ShoppingEventResponse(ShoppingEventBase): id: int created_at: datetime updated_at: Optional[datetime] = None shop: Shop products: List[ProductWithEventData] = [] class Config: from_attributes = True # Statistics schemas class CategoryStats(BaseModel): category: str total_spent: float item_count: int avg_price: float class ShopStats(BaseModel): shop_name: str total_spent: float visit_count: int avg_per_visit: float # Update forward references BrandInShop.model_rebuild() # Related Products schemas class RelatedProductBase(BaseModel): product_id: int related_product_id: int relationship_type: Optional[str] = None class RelatedProductCreate(RelatedProductBase): pass class RelatedProductUpdate(BaseModel): relationship_type: Optional[str] = None class RelatedProduct(RelatedProductBase): id: int created_at: datetime class Config: from_attributes = True # Product with related products class ProductWithRelated(Product): related_products: List["Product"] = [] class Config: from_attributes = True # Update forward references for classes that reference other classes ProductWithRelated.model_rebuild()