# models/schema/customer.py """ Pydantic schema for customer-related operations. """ from datetime import datetime from decimal import Decimal 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: str | None = Field(None, max_length=50) marketing_consent: bool = Field(default=False) preferred_language: str | None = Field( None, description="Preferred language (en, fr, de, lb)" ) @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: EmailStr | None = None first_name: str | None = Field(None, min_length=1, max_length=100) last_name: str | None = Field(None, min_length=1, max_length=100) phone: str | None = Field(None, max_length=50) marketing_consent: bool | None = None preferred_language: str | None = Field( None, description="Preferred language (en, fr, de, lb)" ) @field_validator("email") @classmethod def email_lowercase(cls, v: str | None) -> str | None: """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: str | None last_name: str | None phone: str | None customer_number: str marketing_consent: bool preferred_language: str | None last_order_date: datetime | None 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: str | None = Field(None, max_length=200) address_line_1: str = Field(..., min_length=1, max_length=255) address_line_2: str | None = 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: str | None = Field(None, pattern="^(billing|shipping)$") first_name: str | None = Field(None, min_length=1, max_length=100) last_name: str | None = Field(None, min_length=1, max_length=100) company: str | None = Field(None, max_length=200) address_line_1: str | None = Field(None, min_length=1, max_length=255) address_line_2: str | None = Field(None, max_length=255) city: str | None = Field(None, min_length=1, max_length=100) postal_code: str | None = Field(None, min_length=1, max_length=20) country: str | None = Field(None, min_length=2, max_length=100) is_default: bool | None = 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: str | None address_line_1: str address_line_2: str | None 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: bool | None = None preferred_language: str | None = Field( None, description="Preferred language (en, fr, de, lb)" ) currency: str | None = Field(None, max_length=3) notification_preferences: dict[str, bool] | None = None # ============================================================================ # Vendor Customer Management Response Schemas # ============================================================================ class CustomerMessageResponse(BaseModel): """Simple message response for customer operations.""" message: str class VendorCustomerListResponse(BaseModel): """Schema for vendor customer list with skip/limit pagination.""" customers: list[CustomerResponse] = [] total: int = 0 skip: int = 0 limit: int = 100 message: str | None = None class CustomerDetailResponse(BaseModel): """Detailed customer response for vendor management.""" id: int | None = None vendor_id: int | None = None email: str | None = None first_name: str | None = None last_name: str | None = None phone: str | None = None customer_number: str | None = None marketing_consent: bool | None = None preferred_language: str | None = None last_order_date: datetime | None = None total_orders: int | None = None total_spent: Decimal | None = None is_active: bool | None = None created_at: datetime | None = None updated_at: datetime | None = None message: str | None = None model_config = {"from_attributes": True} class CustomerOrderInfo(BaseModel): """Basic order info for customer order history.""" id: int order_number: str status: str total: Decimal created_at: datetime class CustomerOrdersResponse(BaseModel): """Response for customer order history.""" orders: list[CustomerOrderInfo] = [] total: int = 0 message: str | None = None class CustomerStatisticsResponse(BaseModel): """Response for customer statistics.""" total_orders: int = 0 total_spent: float = 0.0 average_order_value: float = 0.0 last_order_date: datetime | None = None message: str | None = None