feat: add mandatory vendor onboarding wizard
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>
This commit is contained in:
72
alembic/versions/m1b2c3d4e5f6_add_vendor_onboarding_table.py
Normal file
72
alembic/versions/m1b2c3d4e5f6_add_vendor_onboarding_table.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""add vendor onboarding table
|
||||
|
||||
Revision ID: m1b2c3d4e5f6
|
||||
Revises: d7a4a3f06394
|
||||
Create Date: 2025-12-27 22:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'm1b2c3d4e5f6'
|
||||
down_revision: Union[str, None] = 'd7a4a3f06394'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table('vendor_onboarding',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('vendor_id', sa.Integer(), nullable=False),
|
||||
# Overall status
|
||||
sa.Column('status', sa.String(length=20), nullable=False, server_default='not_started'),
|
||||
sa.Column('current_step', sa.String(length=30), nullable=False, server_default='company_profile'),
|
||||
# Step 1: Company Profile
|
||||
sa.Column('step_company_profile_completed', sa.Boolean(), nullable=False, server_default=sa.text('0')),
|
||||
sa.Column('step_company_profile_completed_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('step_company_profile_data', sa.JSON(), nullable=True),
|
||||
# Step 2: Letzshop API Configuration
|
||||
sa.Column('step_letzshop_api_completed', sa.Boolean(), nullable=False, server_default=sa.text('0')),
|
||||
sa.Column('step_letzshop_api_completed_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('step_letzshop_api_connection_verified', sa.Boolean(), nullable=False, server_default=sa.text('0')),
|
||||
# Step 3: Product Import
|
||||
sa.Column('step_product_import_completed', sa.Boolean(), nullable=False, server_default=sa.text('0')),
|
||||
sa.Column('step_product_import_completed_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('step_product_import_csv_url_set', sa.Boolean(), nullable=False, server_default=sa.text('0')),
|
||||
# Step 4: Order Sync
|
||||
sa.Column('step_order_sync_completed', sa.Boolean(), nullable=False, server_default=sa.text('0')),
|
||||
sa.Column('step_order_sync_completed_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('step_order_sync_job_id', sa.Integer(), nullable=True),
|
||||
# Completion tracking
|
||||
sa.Column('started_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
|
||||
# Admin override
|
||||
sa.Column('skipped_by_admin', sa.Boolean(), nullable=False, server_default=sa.text('0')),
|
||||
sa.Column('skipped_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('skipped_reason', sa.Text(), nullable=True),
|
||||
sa.Column('skipped_by_user_id', sa.Integer(), nullable=True),
|
||||
# Timestamps
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
# Constraints
|
||||
sa.ForeignKeyConstraint(['vendor_id'], ['vendors.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['skipped_by_user_id'], ['users.id']),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sqlite_autoincrement=True
|
||||
)
|
||||
op.create_index(op.f('ix_vendor_onboarding_id'), 'vendor_onboarding', ['id'], unique=False)
|
||||
op.create_index(op.f('ix_vendor_onboarding_vendor_id'), 'vendor_onboarding', ['vendor_id'], unique=True)
|
||||
op.create_index(op.f('ix_vendor_onboarding_status'), 'vendor_onboarding', ['status'], unique=False)
|
||||
op.create_index('idx_onboarding_vendor_status', 'vendor_onboarding', ['vendor_id', 'status'], unique=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index('idx_onboarding_vendor_status', table_name='vendor_onboarding')
|
||||
op.drop_index(op.f('ix_vendor_onboarding_status'), table_name='vendor_onboarding')
|
||||
op.drop_index(op.f('ix_vendor_onboarding_vendor_id'), table_name='vendor_onboarding')
|
||||
op.drop_index(op.f('ix_vendor_onboarding_id'), table_name='vendor_onboarding')
|
||||
op.drop_table('vendor_onboarding')
|
||||
Reference in New Issue
Block a user