from datetime import datetime from typing import Any, Dict, List, Literal, Optional from pydantic import BaseModel, Field, field_validator ShipmentStatus = Literal[ "draft", "submitted", "booked", "in_transit", "delivered", "cancelled", "failed", ] class ShipmondoAddress(BaseModel): recipient_name: str = Field(min_length=2, max_length=150) company_name: Optional[str] = Field(default=None, max_length=150) address_line1: str = Field(min_length=2, max_length=200) address_line2: Optional[str] = Field(default=None, max_length=200) postal_code: str = Field(min_length=2, max_length=20) city: str = Field(min_length=2, max_length=120) country_code: str = Field(min_length=2, max_length=2) phone: Optional[str] = Field(default=None, max_length=50) email: Optional[str] = Field(default=None, max_length=150) @field_validator("country_code") @classmethod def normalize_country_code(cls, value: str) -> str: return value.strip().upper() class ShipmondoParcelInput(BaseModel): weight_kg: float = Field(gt=0, le=2000) length_cm: Optional[float] = Field(default=None, gt=0, le=400) width_cm: Optional[float] = Field(default=None, gt=0, le=400) height_cm: Optional[float] = Field(default=None, gt=0, le=400) description: str = Field(min_length=1, max_length=255) class ShipmondoBookingCreate(BaseModel): case_id: int = Field(gt=0) customer_id: Optional[int] = Field(default=None, gt=0) contact_id: Optional[int] = Field(default=None, gt=0) product_code: str = Field(min_length=1, max_length=80) product_name: Optional[str] = Field(default=None, max_length=180) carrier_code: Optional[str] = Field(default=None, max_length=80) carrier_name: Optional[str] = Field(default=None, max_length=120) service_codes: List[str] = Field(default_factory=list, max_length=30) own_agreement: bool = False automatic_select_service_point: bool = False address: ShipmondoAddress parcels: List[ShipmondoParcelInput] = Field(min_length=1, max_length=30) @field_validator("service_codes") @classmethod def normalize_service_codes(cls, values: List[str]) -> List[str]: return list(dict.fromkeys(value.strip().upper() for value in values if value.strip())) class ShipmondoBookingResponse(BaseModel): id: int booking_ref: str case_id: int customer_id: Optional[int] = None contact_id: Optional[int] = None product_code: str product_name: Optional[str] = None carrier_code: Optional[str] = None carrier_name: Optional[str] = None service_codes: List[str] = Field(default_factory=list) shipment_status: ShipmentStatus recipient_name: str city: str country_code: str tracking_number: Optional[str] = None tracking_url: Optional[str] = None label_url: Optional[str] = None total_amount: Optional[float] = None currency: Optional[str] = None dry_run: bool submitted_at: Optional[datetime] = None created_at: datetime updated_at: datetime parcels: List[ShipmondoParcelInput] = Field(default_factory=list) class ShipmondoBookingListResponse(BaseModel): items: List[ShipmondoBookingResponse] class ShipmondoBookingSubmitResponse(BaseModel): booking_ref: str status: ShipmentStatus dry_run: bool tracking_number: Optional[str] = None tracking_url: Optional[str] = None label_url: Optional[str] = None total_amount: Optional[float] = None currency: Optional[str] = None class ShipmondoProductListResponse(BaseModel): items: List[Dict[str, Any]]