# models/schema/onboarding.py """ Pydantic schemas for Vendor Onboarding operations. Schemas include: - OnboardingStatusResponse: Current onboarding status with all step states - CompanyProfileRequest/Response: Step 1 - Company profile data - LetzshopApiConfigRequest/Response: Step 2 - API configuration - ProductImportConfigRequest/Response: Step 3 - CSV URL configuration - OrderSyncTriggerResponse: Step 4 - Job trigger response - OrderSyncProgressResponse: Step 4 - Progress polling response """ from datetime import datetime from pydantic import BaseModel, ConfigDict, Field # ============================================================================= # STEP STATUS MODELS # ============================================================================= class StepStatus(BaseModel): """Status for a single onboarding step.""" completed: bool = False completed_at: datetime | None = None class CompanyProfileStepStatus(StepStatus): """Step 1 status with saved data.""" data: dict | None = None class LetzshopApiStepStatus(StepStatus): """Step 2 status with connection verification.""" connection_verified: bool = False class ProductImportStepStatus(StepStatus): """Step 3 status with CSV URL flag.""" csv_url_set: bool = False class OrderSyncStepStatus(StepStatus): """Step 4 status with job tracking.""" job_id: int | None = None # ============================================================================= # ONBOARDING STATUS RESPONSE # ============================================================================= class OnboardingStatusResponse(BaseModel): """Full onboarding status with all step information.""" model_config = ConfigDict(from_attributes=True) id: int vendor_id: int status: str # not_started, in_progress, completed, skipped current_step: str # company_profile, letzshop_api, product_import, order_sync # Step statuses company_profile: CompanyProfileStepStatus letzshop_api: LetzshopApiStepStatus product_import: ProductImportStepStatus order_sync: OrderSyncStepStatus # Progress tracking completion_percentage: int completed_steps_count: int total_steps: int = 4 # Completion info is_completed: bool started_at: datetime | None = None completed_at: datetime | None = None # Admin override info skipped_by_admin: bool = False skipped_at: datetime | None = None skipped_reason: str | None = None # ============================================================================= # STEP 1: COMPANY PROFILE # ============================================================================= class CompanyProfileRequest(BaseModel): """Request to save company profile during onboarding Step 1.""" # Company name is already set during signup, but can be updated company_name: str | None = Field(None, min_length=2, max_length=255) # Vendor/brand name brand_name: str | None = Field(None, min_length=2, max_length=255) description: str | None = Field(None, max_length=2000) # Contact information contact_email: str | None = Field(None, max_length=255) contact_phone: str | None = Field(None, max_length=50) website: str | None = Field(None, max_length=255) business_address: str | None = Field(None, max_length=500) tax_number: str | None = Field(None, max_length=100) # Language preferences default_language: str = Field("fr", pattern="^(en|fr|de|lb)$") dashboard_language: str = Field("fr", pattern="^(en|fr|de|lb)$") class CompanyProfileResponse(BaseModel): """Response after saving company profile.""" success: bool step_completed: bool next_step: str | None = None message: str | None = None # ============================================================================= # STEP 2: LETZSHOP API CONFIGURATION # ============================================================================= class LetzshopApiConfigRequest(BaseModel): """Request to configure Letzshop API credentials.""" api_key: str = Field(..., min_length=10, description="Letzshop API key") shop_slug: str = Field( ..., min_length=2, max_length=100, description="Letzshop shop URL slug (e.g., 'my-shop')", ) vendor_id: str | None = Field( None, max_length=100, description="Letzshop vendor ID (optional, auto-detected if not provided)", ) class LetzshopApiTestRequest(BaseModel): """Request to test Letzshop API connection.""" api_key: str = Field(..., min_length=10) shop_slug: str = Field(..., min_length=2, max_length=100) class LetzshopApiTestResponse(BaseModel): """Response from Letzshop API connection test.""" success: bool message: str vendor_name: str | None = None vendor_id: str | None = None shop_slug: str | None = None class LetzshopApiConfigResponse(BaseModel): """Response after saving Letzshop API configuration.""" success: bool step_completed: bool next_step: str | None = None message: str | None = None connection_verified: bool = False # ============================================================================= # STEP 3: PRODUCT & ORDER IMPORT CONFIGURATION # ============================================================================= class ProductImportConfigRequest(BaseModel): """Request to configure product import settings.""" # CSV feed URLs for each language csv_url_fr: str | None = Field(None, max_length=500) csv_url_en: str | None = Field(None, max_length=500) csv_url_de: str | None = Field(None, max_length=500) # Letzshop feed settings default_tax_rate: int = Field( 17, ge=0, le=17, description="Default VAT rate: 0, 3, 8, 14, or 17", ) delivery_method: str = Field( "package_delivery", description="Delivery method: nationwide, package_delivery, self_collect", ) preorder_days: int = Field(1, ge=0, le=30) class ProductImportConfigResponse(BaseModel): """Response after saving product import configuration.""" success: bool step_completed: bool next_step: str | None = None message: str | None = None csv_urls_configured: int = 0 # ============================================================================= # STEP 4: ORDER SYNC # ============================================================================= class OrderSyncTriggerRequest(BaseModel): """Request to trigger historical order import.""" # How far back to import orders (days) days_back: int = Field(90, ge=1, le=365, description="Days of order history to import") include_products: bool = Field(True, description="Also import products from Letzshop") class OrderSyncTriggerResponse(BaseModel): """Response after triggering order sync job.""" success: bool message: str job_id: int | None = None estimated_duration_minutes: int | None = None class OrderSyncProgressResponse(BaseModel): """Response for order sync progress polling.""" job_id: int status: str # pending, running, completed, failed progress_percentage: int = 0 current_phase: str | None = None # products, orders, finalizing # Counts orders_imported: int = 0 orders_total: int | None = None products_imported: int = 0 # Timing started_at: datetime | None = None completed_at: datetime | None = None estimated_remaining_seconds: int | None = None # Error info if failed error_message: str | None = None class OrderSyncCompleteRequest(BaseModel): """Request to mark order sync as complete.""" job_id: int class OrderSyncCompleteResponse(BaseModel): """Response after completing order sync step.""" success: bool step_completed: bool onboarding_completed: bool = False message: str | None = None redirect_url: str | None = None # ============================================================================= # ADMIN SKIP # ============================================================================= class OnboardingSkipRequest(BaseModel): """Request to skip onboarding (admin only).""" reason: str = Field(..., min_length=10, max_length=500) class OnboardingSkipResponse(BaseModel): """Response after skipping onboarding.""" success: bool message: str vendor_id: int skipped_at: datetime