Initial commit: TradeMate 外贸小助手 MVP
项目结构: - backend/ Python FastAPI 后端 - uni-app/ uni-app跨端前端 - docs/ 设计文档 - docker-compose.yml Docker编排 - nginx/scripts/systemd 运维配置 已完成功能: - 用户认证 (JWT) - 智能翻译 + 回复建议 - 营销素材生成 - 客户管理 + 沉默检测 - 报价单管理 - 产品库管理 - 汇率换算 - 推送通知 (uni-push) - WhatsApp Webhook框架 - Celery定时任务
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
"""initial schema
|
||||
|
||||
Revision ID: 001
|
||||
Revises:
|
||||
Create Date: 2026-05-08
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = '001'
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table('users',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('wechat_openid', sa.String(length=255), nullable=True),
|
||||
sa.Column('phone', sa.String(length=20), nullable=True),
|
||||
sa.Column('username', sa.String(length=100), nullable=True),
|
||||
sa.Column('password_hash', sa.String(length=255), nullable=True),
|
||||
sa.Column('tier', sa.String(length=50), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('settings', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_users_phone'), 'users', ['phone'], unique=True)
|
||||
op.create_index(op.f('ix_users_wechat_openid'), 'users', ['wechat_openid'], unique=True)
|
||||
|
||||
op.create_table('products',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=False),
|
||||
sa.Column('name_en', sa.String(length=255), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('description_en', sa.Text(), nullable=True),
|
||||
sa.Column('category', sa.String(length=100), nullable=True),
|
||||
sa.Column('price', sa.String(length=50), nullable=True),
|
||||
sa.Column('price_unit', sa.String(length=20), nullable=True),
|
||||
sa.Column('moq', sa.String(length=50), nullable=True),
|
||||
sa.Column('keywords', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column('specifications', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column('images', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_products_user_id'), 'products', ['user_id'], unique=False)
|
||||
|
||||
op.create_table('customers',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('name', sa.String(length=255), nullable=False),
|
||||
sa.Column('company', sa.String(length=255), nullable=True),
|
||||
sa.Column('country', sa.String(length=100), nullable=True),
|
||||
sa.Column('phone', sa.String(length=50), nullable=True),
|
||||
sa.Column('email', sa.String(length=255), nullable=True),
|
||||
sa.Column('whatsapp_id', sa.String(length=255), nullable=True),
|
||||
sa.Column('source', sa.String(length=100), nullable=True),
|
||||
sa.Column('tags', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column('notes', sa.Text(), nullable=True),
|
||||
sa.Column('preference', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column('status', sa.String(length=50), nullable=True),
|
||||
sa.Column('last_contact_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('silence_started_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('next_followup_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('estimated_value', sa.String(length=50), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_customers_user_id'), 'customers', ['user_id'], unique=False)
|
||||
|
||||
op.create_table('conversations',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('customer_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('channel', sa.String(length=50), nullable=True),
|
||||
sa.Column('topic', sa.String(length=255), nullable=True),
|
||||
sa.Column('status', sa.String(length=50), nullable=True),
|
||||
sa.Column('message_count', sa.Integer(), nullable=True),
|
||||
sa.Column('last_message_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['customer_id'], ['customers.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_conversations_customer_id'), 'conversations', ['customer_id'], unique=False)
|
||||
op.create_index(op.f('ix_conversations_user_id'), 'conversations', ['user_id'], unique=False)
|
||||
|
||||
op.create_table('messages',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('conversation_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('direction', sa.String(length=20), nullable=False),
|
||||
sa.Column('content', sa.Text(), nullable=False),
|
||||
sa.Column('content_translated', sa.Text(), nullable=True),
|
||||
sa.Column('content_type', sa.String(length=50), nullable=True),
|
||||
sa.Column('ai_suggestions', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column('selected_suggestion', sa.Integer(), nullable=True),
|
||||
sa.Column('user_edited', sa.Text(), nullable=True),
|
||||
sa.Column('status', sa.String(length=50), nullable=True),
|
||||
sa.Column('metadata', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_messages_conversation_id'), 'messages', ['conversation_id'], unique=False)
|
||||
|
||||
op.create_table('quotations',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('customer_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('title', sa.String(length=255), nullable=True),
|
||||
sa.Column('status', sa.String(length=50), nullable=True),
|
||||
sa.Column('currency', sa.String(length=10), nullable=True),
|
||||
sa.Column('exchange_rate', sa.Float(), nullable=True),
|
||||
sa.Column('payment_terms', sa.String(length=255), nullable=True),
|
||||
sa.Column('delivery_terms', sa.String(length=255), nullable=True),
|
||||
sa.Column('lead_time', sa.String(length=100), nullable=True),
|
||||
sa.Column('valid_until', sa.String(length=100), nullable=True),
|
||||
sa.Column('subtotal', sa.Float(), nullable=True),
|
||||
sa.Column('discount', sa.Float(), nullable=True),
|
||||
sa.Column('shipping', sa.Float(), nullable=True),
|
||||
sa.Column('total', sa.Float(), nullable=True),
|
||||
sa.Column('notes', sa.Text(), nullable=True),
|
||||
sa.Column('pdf_url', sa.Text(), nullable=True),
|
||||
sa.Column('sent_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['customer_id'], ['customers.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_quotations_user_id'), 'quotations', ['user_id'], unique=False)
|
||||
|
||||
op.create_table('quotation_items',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('quotation_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('product_name', sa.String(length=255), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('quantity', sa.Integer(), nullable=False),
|
||||
sa.Column('unit_price', sa.Float(), nullable=False),
|
||||
sa.Column('total_price', sa.Float(), nullable=True),
|
||||
sa.Column('unit', sa.String(length=50), nullable=True),
|
||||
sa.ForeignKeyConstraint(['quotation_id'], ['quotations.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_quotation_items_quotation_id'), 'quotation_items', ['quotation_id'], unique=False)
|
||||
|
||||
op.create_table('corpus_entries',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('source_text', sa.Text(), nullable=False),
|
||||
sa.Column('target_text', sa.Text(), nullable=False),
|
||||
sa.Column('source_lang', sa.String(length=20), nullable=True),
|
||||
sa.Column('target_lang', sa.String(length=20), nullable=True),
|
||||
sa.Column('task_type', sa.String(length=50), nullable=False),
|
||||
sa.Column('domain', sa.String(length=100), nullable=True),
|
||||
sa.Column('provider_used', sa.String(length=50), nullable=True),
|
||||
sa.Column('quality_score', sa.Float(), nullable=True),
|
||||
sa.Column('user_edited', sa.Boolean(), nullable=True),
|
||||
sa.Column('user_rating', sa.Integer(), nullable=True),
|
||||
sa.Column('usage_count', sa.Integer(), nullable=True),
|
||||
sa.Column('embedding', postgresql.Vector(length=768), nullable=True),
|
||||
sa.Column('metadata', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table('corpus_entries')
|
||||
op.drop_table('quotation_items')
|
||||
op.drop_table('quotations')
|
||||
op.drop_table('messages')
|
||||
op.drop_table('conversations')
|
||||
op.drop_table('customers')
|
||||
op.drop_table('products')
|
||||
op.drop_table('users')
|
||||
Reference in New Issue
Block a user