groceries/backend/schemas.py
lasse 0b42a74fe9 Minor version bump (1.x.0) is appropriate because:
 New functionality added (soft delete system)
 Backward compatible (existing features unchanged)
 Significant enhancement (complete temporal tracking system)
 API additions (new endpoints, parameters)
 UI enhancements (new components, visual indicators)
2025-05-30 09:49:26 +02:00

255 lines
6.1 KiB
Python

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()