import json from pydantic import BaseModel, model_validator, field_validator from typing import Optional, List, Any class CategoryCreate(BaseModel): name: str color: Optional[str] = None sort_order: int = 0 parent_id: Optional[int] = None general_sort_order: int = 0 auto_expanded: bool = False class CategoryUpdate(BaseModel): name: Optional[str] = None color: Optional[str] = None sort_order: Optional[int] = None parent_id: Optional[int] = None general_sort_order: Optional[int] = None auto_expanded: Optional[bool] = None class CategoryOut(BaseModel): id: int name: str color: Optional[str] = None sort_order: int = 0 parent_id: Optional[int] = None general_sort_order: int = 0 auto_expanded: bool = False model_config = {"from_attributes": True} class CategoryReorderItem(BaseModel): id: int sort_order: int class SubcategoryReorderItem(BaseModel): id: int sort_order: int # position among subcategories within the parent class ParentGeneralReorderItem(BaseModel): id: int # parent category id general_sort_order: int # ── Quick Options ───────────────────────────────────────────────────────────── class ProductQuickOptionCreate(BaseModel): name: str price: float = 0.0 allow_multiple: bool = False sort_order: int = 0 is_favorite: bool = False favorite_sort_order: int = 0 is_compact: bool = False class ProductQuickOptionOut(BaseModel): id: int product_id: int name: str price: float = 0.0 allow_multiple: bool = False sort_order: int = 0 is_favorite: bool = False favorite_sort_order: int = 0 is_compact: bool = False model_config = {"from_attributes": True} # ── Options ────────────────────────────────────────────────────────────────── class OptionSubChoice(BaseModel): name: str extra_cost: float = 0.0 is_default: bool = False class ProductOptionBase(BaseModel): name: str extra_cost: float = 0.0 allow_multiple: bool = False is_favorite: bool = False favorite_sort_order: int = 0 class ProductOptionCreate(ProductOptionBase): sub_choices: List[OptionSubChoice] = [] class ProductOptionOut(ProductOptionBase): id: int product_id: int sub_choices: List[OptionSubChoice] = [] model_config = {"from_attributes": True} @model_validator(mode='before') @classmethod def parse_option_sub_choices(cls, data: Any) -> Any: if hasattr(data, 'sub_choices'): raw = data.sub_choices parsed = json.loads(raw) if isinstance(raw, str) else [] return { 'id': data.id, 'product_id': data.product_id, 'name': data.name, 'extra_cost': data.extra_cost, 'allow_multiple': getattr(data, 'allow_multiple', False) or False, 'sub_choices': parsed, 'is_favorite': getattr(data, 'is_favorite', False) or False, 'favorite_sort_order': getattr(data, 'favorite_sort_order', 0) or 0, } return data # ── Ingredients ─────────────────────────────────────────────────────────────── class ProductIngredientBase(BaseModel): name: str extra_cost: float = 0.0 is_favorite: bool = False favorite_sort_order: int = 0 class ProductIngredientCreate(ProductIngredientBase): pass class ProductIngredientOut(ProductIngredientBase): id: int product_id: int model_config = {"from_attributes": True} # ── Sub-choices (nested under a preference choice) ──────────────────────────── class SubChoice(BaseModel): name: str extra_cost: float = 0.0 is_default: bool = False # ── Shared subset (set-level, shown for all non-disabling choices) ───────────── class SharedSubsetChoice(BaseModel): name: str extra_cost: float = 0.0 is_default: bool = False class SharedSubset(BaseModel): name: str choices: List[SharedSubsetChoice] = [] # ── Preferences ─────────────────────────────────────────────────────────────── class PreferenceChoiceCreate(BaseModel): name: str extra_cost: float = 0.0 sub_choices: List[SubChoice] = [] disables_subset: bool = False class PreferenceChoiceOut(BaseModel): id: int set_id: int name: str extra_cost: float = 0.0 sub_choices: List[SubChoice] = [] disables_subset: bool = False model_config = {"from_attributes": True} @model_validator(mode='before') @classmethod def parse_sub_choices(cls, data: Any) -> Any: if hasattr(data, 'sub_choices'): raw = data.sub_choices if isinstance(raw, str): try: parsed = json.loads(raw) except Exception: parsed = [] else: parsed = [] return { 'id': data.id, 'set_id': data.set_id, 'name': data.name, 'extra_cost': data.extra_cost, 'sub_choices': parsed, 'disables_subset': data.disables_subset or False, } return data class PreferenceSetCreate(BaseModel): name: str choices: List[PreferenceChoiceCreate] = [] default_choice_index: Optional[int] = None # index into choices (0-based) shared_subset: Optional[SharedSubset] = None is_favorite: bool = False favorite_sort_order: int = 0 class PreferenceSetOut(BaseModel): id: int product_id: int name: str choices: List[PreferenceChoiceOut] = [] default_choice_id: Optional[int] = None shared_subset: Optional[SharedSubset] = None is_favorite: bool = False favorite_sort_order: int = 0 model_config = {"from_attributes": True} @model_validator(mode='before') @classmethod def parse_shared_subset(cls, data: Any) -> Any: if hasattr(data, 'shared_subset'): raw = data.shared_subset if isinstance(raw, str): try: parsed = json.loads(raw) except Exception: parsed = None else: parsed = None return { 'id': data.id, 'product_id': data.product_id, 'name': data.name, 'choices': list(data.choices), 'default_choice_id': data.default_choice_id, 'shared_subset': parsed, 'is_favorite': getattr(data, 'is_favorite', False) or False, 'favorite_sort_order': getattr(data, 'favorite_sort_order', 0) or 0, } return data # ── Products ────────────────────────────────────────────────────────────────── class ProductBase(BaseModel): name: str category_id: Optional[int] = None base_price: float is_available: bool = True lifecycle_status: str = "active" printer_zone_id: Optional[int] = None sort_order: int = 0 class ProductCreate(ProductBase): quick_options: List[ProductQuickOptionCreate] = [] options: List[ProductOptionCreate] = [] ingredients: List[ProductIngredientCreate] = [] preference_sets: List[PreferenceSetCreate] = [] class ProductUpdate(BaseModel): name: Optional[str] = None category_id: Optional[int] = None base_price: Optional[float] = None is_available: Optional[bool] = None lifecycle_status: Optional[str] = None printer_zone_id: Optional[int] = None sort_order: Optional[int] = None quick_options: Optional[List[ProductQuickOptionCreate]] = None options: Optional[List[ProductOptionCreate]] = None ingredients: Optional[List[ProductIngredientCreate]] = None preference_sets: Optional[List[PreferenceSetCreate]] = None class ProductReorderItem(BaseModel): id: int sort_order: int class ProductOut(ProductBase): id: int quick_options: List[ProductQuickOptionOut] = [] options: List[ProductOptionOut] = [] ingredients: List[ProductIngredientOut] = [] preference_sets: List[PreferenceSetOut] = [] image_url: Optional[str] = None model_config = {"from_attributes": True}