- Fix JS-008: Replace raw fetch() with apiClient in letzshop-vendor-directory.js - Fix JS-005: Add init guard to letzshop-vendor-directory.js - Fix JS-004: Increase search region in validator (800→2000 chars) to detect currentPage in files with setup code before return statement - Fix JS-001: Use centralized logger in media-picker.js - Fix API-002: Move database query from onboarding.py to order_service.py - Fix FE-001: Add noqa comment to search.html (shop uses custom themed pagination) - Add audit validator to validate_all.py script - Update frontend.yaml with vendor exclusion pattern Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
290 lines
9.0 KiB
Python
290 lines
9.0 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, BackgroundTasks, 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 app.tasks.letzshop_tasks import process_historical_import
|
|
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)
|
|
result = 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,
|
|
)
|
|
db.commit() # Commit at API level for transaction control
|
|
return result
|
|
|
|
|
|
# =============================================================================
|
|
# 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)
|
|
result = 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,
|
|
)
|
|
db.commit() # Commit at API level for transaction control
|
|
return result
|
|
|
|
|
|
# =============================================================================
|
|
# 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)
|
|
result = 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,
|
|
)
|
|
db.commit() # Commit at API level for transaction control
|
|
return result
|
|
|
|
|
|
# =============================================================================
|
|
# Step 4: Order Sync
|
|
# =============================================================================
|
|
|
|
|
|
@router.post("/step/order-sync/trigger", response_model=OrderSyncTriggerResponse)
|
|
def trigger_order_sync(
|
|
request: OrderSyncTriggerRequest,
|
|
background_tasks: BackgroundTasks,
|
|
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)
|
|
result = 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,
|
|
)
|
|
db.commit() # Commit at API level for transaction control
|
|
|
|
# Queue background task to process the import
|
|
if result.get("success") and result.get("job_id"):
|
|
from app.tasks.dispatcher import task_dispatcher
|
|
|
|
celery_task_id = task_dispatcher.dispatch_historical_import(
|
|
background_tasks=background_tasks,
|
|
job_id=result["job_id"],
|
|
vendor_id=current_user.token_vendor_id,
|
|
)
|
|
|
|
# Store Celery task ID if using Celery
|
|
if celery_task_id:
|
|
from app.services.letzshop import LetzshopOrderService
|
|
|
|
order_service = LetzshopOrderService(db)
|
|
order_service.update_job_celery_task_id(result["job_id"], celery_task_id)
|
|
|
|
logger.info(f"Queued historical import task for job {result['job_id']}")
|
|
|
|
return result
|
|
|
|
|
|
@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)
|
|
result = service.complete_order_sync(
|
|
vendor_id=current_user.token_vendor_id,
|
|
job_id=request.job_id,
|
|
)
|
|
db.commit() # Commit at API level for transaction control
|
|
return result
|