Files
orion/app/api/v1/vendor/onboarding.py
Samir Boulahtit 73f612a01a fix: use custom exceptions in onboarding and add tests
- Create onboarding-specific exceptions (OnboardingNotFoundException, etc.)
- Remove HTTPException usage from API endpoints per architecture rules
- Let exceptions propagate to global handler
- Add 12 integration tests for onboarding API endpoints

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 21:55:03 +01:00

258 lines
7.7 KiB
Python

# app/api/v1/vendor/onboarding.py
"""
Vendor onboarding API endpoints.
Provides endpoints for the 4-step mandatory onboarding wizard:
1. Company Profile Setup
2. Letzshop API Configuration
3. Product & Order Import Configuration
4. Order Sync (historical import)
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
"""
import logging
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.services.onboarding_service import OnboardingService
from models.database.user import User
from models.schema.onboarding import (
CompanyProfileRequest,
CompanyProfileResponse,
LetzshopApiConfigRequest,
LetzshopApiConfigResponse,
LetzshopApiTestRequest,
LetzshopApiTestResponse,
OnboardingStatusResponse,
OrderSyncCompleteRequest,
OrderSyncCompleteResponse,
OrderSyncProgressResponse,
OrderSyncTriggerRequest,
OrderSyncTriggerResponse,
ProductImportConfigRequest,
ProductImportConfigResponse,
)
router = APIRouter(prefix="/onboarding")
logger = logging.getLogger(__name__)
# =============================================================================
# Status Endpoint
# =============================================================================
@router.get("/status", response_model=OnboardingStatusResponse)
def get_onboarding_status(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get current onboarding status.
Returns full status including all step completion states and progress.
"""
service = OnboardingService(db)
status = service.get_status_response(current_user.token_vendor_id)
return status
# =============================================================================
# Step 1: Company Profile
# =============================================================================
@router.get("/step/company-profile")
def get_company_profile(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get current company profile data for editing.
Returns pre-filled data from vendor and company records.
"""
service = OnboardingService(db)
return service.get_company_profile_data(current_user.token_vendor_id)
@router.post("/step/company-profile", response_model=CompanyProfileResponse)
def save_company_profile(
request: CompanyProfileRequest,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Save company profile and complete Step 1.
Updates vendor and company records with provided data.
"""
service = OnboardingService(db)
return service.complete_company_profile(
vendor_id=current_user.token_vendor_id,
company_name=request.company_name,
brand_name=request.brand_name,
description=request.description,
contact_email=request.contact_email,
contact_phone=request.contact_phone,
website=request.website,
business_address=request.business_address,
tax_number=request.tax_number,
default_language=request.default_language,
dashboard_language=request.dashboard_language,
)
# =============================================================================
# Step 2: Letzshop API Configuration
# =============================================================================
@router.post("/step/letzshop-api/test", response_model=LetzshopApiTestResponse)
def test_letzshop_api(
request: LetzshopApiTestRequest,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Test Letzshop API connection without saving.
Use this to validate API key before saving credentials.
"""
service = OnboardingService(db)
return service.test_letzshop_api(
api_key=request.api_key,
shop_slug=request.shop_slug,
)
@router.post("/step/letzshop-api", response_model=LetzshopApiConfigResponse)
def save_letzshop_api(
request: LetzshopApiConfigRequest,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Save Letzshop API credentials and complete Step 2.
Tests connection first, only saves if successful.
"""
service = OnboardingService(db)
return service.complete_letzshop_api(
vendor_id=current_user.token_vendor_id,
api_key=request.api_key,
shop_slug=request.shop_slug,
letzshop_vendor_id=request.vendor_id,
)
# =============================================================================
# Step 3: Product & Order Import Configuration
# =============================================================================
@router.get("/step/product-import")
def get_product_import_config(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get current product import configuration.
Returns pre-filled CSV URLs and Letzshop feed settings.
"""
service = OnboardingService(db)
return service.get_product_import_config(current_user.token_vendor_id)
@router.post("/step/product-import", response_model=ProductImportConfigResponse)
def save_product_import_config(
request: ProductImportConfigRequest,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Save product import configuration and complete Step 3.
At least one CSV URL must be provided.
"""
service = OnboardingService(db)
return service.complete_product_import(
vendor_id=current_user.token_vendor_id,
csv_url_fr=request.csv_url_fr,
csv_url_en=request.csv_url_en,
csv_url_de=request.csv_url_de,
default_tax_rate=request.default_tax_rate,
delivery_method=request.delivery_method,
preorder_days=request.preorder_days,
)
# =============================================================================
# Step 4: Order Sync
# =============================================================================
@router.post("/step/order-sync/trigger", response_model=OrderSyncTriggerResponse)
def trigger_order_sync(
request: OrderSyncTriggerRequest,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Trigger historical order import.
Creates a background job that imports orders from Letzshop.
"""
service = OnboardingService(db)
return service.trigger_order_sync(
vendor_id=current_user.token_vendor_id,
user_id=current_user.id,
days_back=request.days_back,
include_products=request.include_products,
)
@router.get(
"/step/order-sync/progress/{job_id}",
response_model=OrderSyncProgressResponse,
)
def get_order_sync_progress(
job_id: int,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get order sync job progress.
Poll this endpoint to show progress bar during import.
"""
service = OnboardingService(db)
return service.get_order_sync_progress(
vendor_id=current_user.token_vendor_id,
job_id=job_id,
)
@router.post("/step/order-sync/complete", response_model=OrderSyncCompleteResponse)
def complete_order_sync(
request: OrderSyncCompleteRequest,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Mark order sync step as complete.
Called after the import job finishes (success or failure).
This also marks the entire onboarding as complete.
"""
service = OnboardingService(db)
return service.complete_order_sync(
vendor_id=current_user.token_vendor_id,
job_id=request.job_id,
)