from datetime import datetime from pydantic import BaseModel, ConfigDict, Field class EditorProductRow(BaseModel): id: int tenant_id: str client_name: str item_id: str | None name: str mix_id: int mix_client_name: str mix_name: str sale_type: str unit_of_measure: str visible: bool product_notes: str | None mix_notes: str | None class EditorProductUpdate(BaseModel): model_config = ConfigDict(extra="forbid") client_name: str | None = Field(default=None, min_length=1, max_length=255) name: str | None = Field(default=None, min_length=1, max_length=255) item_id: str | None = Field(default=None, max_length=128) mix_id: int | None = None sale_type: str | None = Field(default=None, min_length=1, max_length=64) unit_of_measure: str | None = Field(default=None, min_length=1, max_length=64) visible: bool | None = None notes: str | None = Field(default=None, max_length=2000) class EditorMixUpdate(BaseModel): model_config = ConfigDict(extra="forbid") client_name: str | None = Field(default=None, min_length=1, max_length=255) name: str | None = Field(default=None, min_length=1, max_length=255) notes: str | None = Field(default=None, max_length=2000) # Toggling a mix's status fans out to the visibility of every product under it. visible: bool | None = None class EditorMixRow(BaseModel): id: int tenant_id: str client_name: str name: str # A mix reads as "Active" when at least one of its products is visible. visible: bool product_count: int visible_product_count: int notes: str | None class EditorMixIngredientRead(BaseModel): id: int raw_material_id: int raw_material_name: str quantity_kg: float notes: str | None class EditorMixFormulaRead(BaseModel): id: int tenant_id: str client_name: str name: str ingredients: list[EditorMixIngredientRead] total_kg: float class EditorMixIngredientCreate(BaseModel): model_config = ConfigDict(extra="forbid") raw_material_id: int quantity_kg: float = Field(gt=0) notes: str | None = Field(default=None, max_length=1000) class EditorMixIngredientUpdate(BaseModel): model_config = ConfigDict(extra="forbid") quantity_kg: float | None = Field(default=None, gt=0) notes: str | None = Field(default=None, max_length=1000) class EditorProductIngredientCreate(BaseModel): model_config = ConfigDict(extra="forbid") raw_material_id: int quantity_kg: float = Field(gt=0) notes: str | None = Field(default=None, max_length=1000) class EditorProductIngredientUpdate(BaseModel): model_config = ConfigDict(extra="forbid") quantity_kg: float | None = Field(default=None, gt=0) notes: str | None = Field(default=None, max_length=1000) class EditorProductIngredientRead(BaseModel): id: int raw_material_id: int raw_material_name: str quantity_kg: float sort_order: int notes: str | None class EditorProductFormulaRead(BaseModel): id: int tenant_id: str client_name: str name: str mix_id: int mix_name: str ingredients: list[EditorProductIngredientRead] total_kg: float # --- Ingredients (raw materials) catalogue ----------------------------------- class EditorIngredientRow(BaseModel): id: int name: str supplier: str | None unit_of_measure: str kg_per_unit: float status: str # Decimal places this ingredient is rounded to in the mix calculator output. rounding_decimals: int notes: str | None cost_per_kg: float | None # How many product/mix formulas currently reference this ingredient. usage_count: int created_at: datetime class EditorIngredientCreate(BaseModel): model_config = ConfigDict(extra="forbid") name: str = Field(min_length=1, max_length=255) supplier: str | None = Field(default=None, max_length=255) unit_of_measure: str = Field(min_length=1, max_length=64) kg_per_unit: float = Field(gt=0) status: str = Field(default="active", max_length=32) rounding_decimals: int = Field(default=2, ge=0, le=6) notes: str | None = Field(default=None, max_length=2000) class EditorIngredientUpdate(BaseModel): model_config = ConfigDict(extra="forbid") name: str | None = Field(default=None, min_length=1, max_length=255) supplier: str | None = Field(default=None, max_length=255) unit_of_measure: str | None = Field(default=None, min_length=1, max_length=64) kg_per_unit: float | None = Field(default=None, gt=0) status: str | None = Field(default=None, max_length=32) rounding_decimals: int | None = Field(default=None, ge=0, le=6) notes: str | None = Field(default=None, max_length=2000)