# models/schema/customer.py """ Pydantic schema for customer-related operations. """ from datetime import datetime from decimal import Decimal from typing import Optional, Dict, Any, List from pydantic import BaseModel, EmailStr, Field, field_validator # ============================================================================ # Customer Registration & Authentication # ============================================================================ class CustomerRegister(BaseModel): """Schema for customer registration.""" email: EmailStr = Field(..., description="Customer email address") password: str = Field( ..., min_length=8, description="Password (minimum 8 characters)" ) first_name: str = Field(..., min_length=1, max_length=100) last_name: str = Field(..., min_length=1, max_length=100) phone: Optional[str] = Field(None, max_length=50) marketing_consent: bool = Field(default=False) @field_validator('email') @classmethod def email_lowercase(cls, v: str) -> str: """Convert email to lowercase.""" return v.lower() @field_validator('password') @classmethod def password_strength(cls, v: str) -> str: """Validate password strength.""" if len(v) < 8: raise ValueError("Password must be at least 8 characters") if not any(char.isdigit() for char in v): raise ValueError("Password must contain at least one digit") if not any(char.isalpha() for char in v): raise ValueError("Password must contain at least one letter") return v class CustomerUpdate(BaseModel): """Schema for updating customer profile.""" email: Optional[EmailStr] = None first_name: Optional[str] = Field(None, min_length=1, max_length=100) last_name: Optional[str] = Field(None, min_length=1, max_length=100) phone: Optional[str] = Field(None, max_length=50) marketing_consent: Optional[bool] = None @field_validator('email') @classmethod def email_lowercase(cls, v: Optional[str]) -> Optional[str]: """Convert email to lowercase.""" return v.lower() if v else None # ============================================================================ # Customer Response # ============================================================================ class CustomerResponse(BaseModel): """Schema for customer response (excludes password).""" id: int vendor_id: int email: str first_name: Optional[str] last_name: Optional[str] phone: Optional[str] customer_number: str marketing_consent: bool last_order_date: Optional[datetime] total_orders: int total_spent: Decimal is_active: bool created_at: datetime updated_at: datetime model_config = { "from_attributes": True } @property def full_name(self) -> str: """Get customer full name.""" if self.first_name and self.last_name: return f"{self.first_name} {self.last_name}" return self.email class CustomerListResponse(BaseModel): """Schema for paginated customer list.""" customers: List[CustomerResponse] total: int page: int per_page: int total_pages: int # ============================================================================ # Customer Address # ============================================================================ class CustomerAddressCreate(BaseModel): """Schema for creating customer address.""" address_type: str = Field(..., pattern="^(billing|shipping)$") first_name: str = Field(..., min_length=1, max_length=100) last_name: str = Field(..., min_length=1, max_length=100) company: Optional[str] = Field(None, max_length=200) address_line_1: str = Field(..., min_length=1, max_length=255) address_line_2: Optional[str] = Field(None, max_length=255) city: str = Field(..., min_length=1, max_length=100) postal_code: str = Field(..., min_length=1, max_length=20) country: str = Field(..., min_length=2, max_length=100) is_default: bool = Field(default=False) class CustomerAddressUpdate(BaseModel): """Schema for updating customer address.""" address_type: Optional[str] = Field(None, pattern="^(billing|shipping)$") first_name: Optional[str] = Field(None, min_length=1, max_length=100) last_name: Optional[str] = Field(None, min_length=1, max_length=100) company: Optional[str] = Field(None, max_length=200) address_line_1: Optional[str] = Field(None, min_length=1, max_length=255) address_line_2: Optional[str] = Field(None, max_length=255) city: Optional[str] = Field(None, min_length=1, max_length=100) postal_code: Optional[str] = Field(None, min_length=1, max_length=20) country: Optional[str] = Field(None, min_length=2, max_length=100) is_default: Optional[bool] = None class CustomerAddressResponse(BaseModel): """Schema for customer address response.""" id: int vendor_id: int customer_id: int address_type: str first_name: str last_name: str company: Optional[str] address_line_1: str address_line_2: Optional[str] city: str postal_code: str country: str is_default: bool created_at: datetime updated_at: datetime model_config = { "from_attributes": True } # ============================================================================ # Customer Preferences # ============================================================================ class CustomerPreferencesUpdate(BaseModel): """Schema for updating customer preferences.""" marketing_consent: Optional[bool] = None language: Optional[str] = Field(None, max_length=10) currency: Optional[str] = Field(None, max_length=3) notification_preferences: Optional[Dict[str, bool]] = None