# 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 class CustomerPasswordChange(BaseModel): """Schema for customer password change.""" current_password: str = Field(..., description="Current password") new_password: str = Field( ..., min_length=8, description="New password (minimum 8 characters)" ) confirm_password: str = Field(..., description="Confirm new password") @field_validator("new_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 # ============================================================================ # 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_name: str = Field(..., min_length=2, max_length=100) country_iso: str = Field(..., min_length=2, max_length=5) 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_name: str | None = Field(None, min_length=2, max_length=100) country_iso: str | None = Field(None, min_length=2, max_length=5) 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_name: str country_iso: str is_default: bool created_at: datetime updated_at: datetime model_config = {"from_attributes": True} class CustomerAddressListResponse(BaseModel): """Schema for customer address list response.""" addresses: list[CustomerAddressResponse] total: int # ============================================================================ # 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: int = 0 active: int = 0 inactive: int = 0 with_orders: int = 0 total_spent: float = 0.0 total_orders: int = 0 avg_order_value: float = 0.0 # ============================================================================ # Admin Customer Management Response Schemas # ============================================================================ class AdminCustomerItem(BaseModel): """Admin customer list item with vendor info.""" id: int vendor_id: int email: str first_name: str | None = None last_name: str | None = None phone: str | None = None customer_number: str marketing_consent: bool = False preferred_language: str | None = None last_order_date: datetime | None = None total_orders: int = 0 total_spent: float = 0.0 is_active: bool = True created_at: datetime updated_at: datetime vendor_name: str | None = None vendor_code: str | None = None model_config = {"from_attributes": True} class CustomerListResponse(BaseModel): """Admin paginated customer list with skip/limit.""" customers: list[AdminCustomerItem] = [] total: int = 0 skip: int = 0 limit: int = 20 class CustomerDetailResponse(AdminCustomerItem): """Detailed customer response for admin.""" pass