Implement 4-step onboarding flow for new vendors after signup: - Step 1: Company profile setup - Step 2: Letzshop API configuration with connection testing - Step 3: Product & order import CSV URL configuration - Step 4: Historical order sync with progress bar Key features: - Blocks dashboard access until completed - Step indicators with visual progress - Resume capability (progress persisted in DB) - Admin skip capability for support cases 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
292 lines
8.3 KiB
Python
292 lines
8.3 KiB
Python
# 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
|