from __future__ import annotations from datetime import date, datetime from typing import Literal from pydantic import BaseModel, ConfigDict, Field, field_validator QuantityType = Literal["bags", "kg"] class ThroughputProductBase(BaseModel): model_config = ConfigDict(extra="forbid") item_id: str | None = Field(default=None, max_length=64) name: str = Field(min_length=1, max_length=255) client_name: str | None = Field(default=None, max_length=255) default_bag_size: float | None = Field(default=None, ge=0) is_bulka_default: bool = False active: bool = True is_stock_item: bool = True notes: str | None = Field(default=None, max_length=2000) class ThroughputProductCreate(ThroughputProductBase): pass class ThroughputProductUpdate(BaseModel): model_config = ConfigDict(extra="forbid") item_id: str | None = Field(default=None, max_length=64) name: str | None = Field(default=None, min_length=1, max_length=255) client_name: str | None = Field(default=None, max_length=255) default_bag_size: float | None = Field(default=None, ge=0) is_bulka_default: bool | None = None active: bool | None = None is_stock_item: bool | None = None notes: str | None = Field(default=None, max_length=2000) class ThroughputProductRead(ThroughputProductBase): id: int tenant_id: str created_at: datetime updated_at: datetime model_config = ConfigDict(from_attributes=True) class ThroughputEntryBase(BaseModel): model_config = ConfigDict(extra="forbid") production_date: date product_id: int | None = None product_name_snapshot: str | None = Field(default=None, max_length=255) bag_size: float | None = Field(default=None, ge=0) scales_checked: bool = True label_correct: bool = True bag_sealed: bool = True pallet_good_condition: bool = True for_order: bool = False for_stock: bool = False job_number: str | None = Field(default=None, max_length=64) stock_quantity: float | None = Field(default=None, ge=0) sample_box_no: str | None = Field(default=None, max_length=64) test_weight_1: float | None = Field(default=None, ge=0) test_weight_2: float | None = Field(default=None, ge=0) test_weight_3: float | None = Field(default=None, ge=0) test_weight_4: float | None = Field(default=None, ge=0) test_weight_5: float | None = Field(default=None, ge=0) quantity: float = Field(ge=0) quantity_type: QuantityType = "bags" staff_name: str | None = Field(default=None, max_length=255) notes: str | None = Field(default=None, max_length=2000) @field_validator("job_number") @classmethod def _normalize_job_number(cls, value: str | None) -> str | None: if value is None: return None stripped = value.strip() return stripped or None @field_validator("staff_name") @classmethod def _normalize_staff(cls, value: str | None) -> str | None: if value is None: return None stripped = value.strip() return stripped or None class ThroughputEntryCreate(ThroughputEntryBase): pass class ThroughputEntryUpdate(BaseModel): model_config = ConfigDict(extra="forbid") production_date: date | None = None product_id: int | None = None product_name_snapshot: str | None = Field(default=None, max_length=255) bag_size: float | None = Field(default=None, ge=0) scales_checked: bool | None = None label_correct: bool | None = None bag_sealed: bool | None = None pallet_good_condition: bool | None = None for_order: bool | None = None for_stock: bool | None = None job_number: str | None = Field(default=None, max_length=64) stock_quantity: float | None = Field(default=None, ge=0) sample_box_no: str | None = Field(default=None, max_length=64) test_weight_1: float | None = Field(default=None, ge=0) test_weight_2: float | None = Field(default=None, ge=0) test_weight_3: float | None = Field(default=None, ge=0) test_weight_4: float | None = Field(default=None, ge=0) test_weight_5: float | None = Field(default=None, ge=0) quantity: float | None = Field(default=None, ge=0) quantity_type: QuantityType | None = None staff_name: str | None = Field(default=None, max_length=255) notes: str | None = Field(default=None, max_length=2000) class ThroughputEntryRead(BaseModel): id: int tenant_id: str production_date: date product_id: int | None product_name_snapshot: str bag_size: float | None scales_checked: bool label_correct: bool bag_sealed: bool pallet_good_condition: bool for_order: bool for_stock: bool job_number: str | None stock_quantity: float | None sample_box_no: str | None test_weight_1: float | None test_weight_2: float | None test_weight_3: float | None test_weight_4: float | None test_weight_5: float | None quantity: float quantity_type: QuantityType calculated_kg: float staff_name: str | None notes: str | None qa_passed: bool created_by: str | None created_at: datetime updated_at: datetime model_config = ConfigDict(from_attributes=True)