110 lines
5.6 KiB
Python
110 lines
5.6 KiB
Python
"""发票管理表迁移
|
|
|
|
创建 invoices(发票主表)和 invoice_orders(发票-订单关联表)。
|
|
|
|
Revision ID: 20260810_20260810
|
|
Revises: 2026080601
|
|
Create Date: 2026-08-10 00:00:00.000000
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
revision: str = '20260810_20260810'
|
|
down_revision: Union[str, None] = '2026080601'
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def _comment_table(table_name: str, comment: str) -> None:
|
|
op.execute(f"COMMENT ON TABLE {table_name} IS '{comment}'")
|
|
|
|
|
|
def _comment_column(table_name: str, column_name: str, comment: str) -> None:
|
|
escaped = comment.replace("'", "''")
|
|
op.execute(f"COMMENT ON COLUMN {table_name}.{column_name} IS '{escaped}'")
|
|
|
|
|
|
def upgrade() -> None:
|
|
# ============================================================
|
|
# 1. 创建 invoices 表
|
|
# ============================================================
|
|
op.create_table(
|
|
'invoices',
|
|
sa.Column('id', sa.String(32), primary_key=True),
|
|
sa.Column('user_id', sa.String(32), sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False),
|
|
sa.Column('invoice_no', sa.String(32), nullable=False, unique=True),
|
|
sa.Column('header_type', sa.String(16), nullable=False),
|
|
sa.Column('header_name', sa.String(128), nullable=False),
|
|
sa.Column('header_tax_no', sa.String(32), nullable=True),
|
|
sa.Column('header_register_address', sa.String(256), nullable=True),
|
|
sa.Column('header_register_phone', sa.String(32), nullable=True),
|
|
sa.Column('header_bank_name', sa.String(128), nullable=True),
|
|
sa.Column('header_bank_account', sa.String(64), nullable=True),
|
|
sa.Column('email', sa.String(128), nullable=False),
|
|
sa.Column('total_amount', sa.Float, nullable=False, server_default='0'),
|
|
sa.Column('total_credits', sa.Float, nullable=False, server_default='0'),
|
|
sa.Column('status', sa.String(16), nullable=False, server_default='processing'),
|
|
sa.Column('failure_reason', sa.Text, nullable=True),
|
|
sa.Column('issued_at', sa.DateTime(timezone=True), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
|
)
|
|
op.create_index('idx_invoices_user_created', 'invoices', ['user_id', 'created_at'])
|
|
op.create_index('idx_invoices_status_created', 'invoices', ['status', 'created_at'])
|
|
op.create_index('idx_invoices_invoice_no', 'invoices', ['invoice_no'], unique=True)
|
|
|
|
# ============================================================
|
|
# 2. 创建 invoice_orders 表
|
|
# ============================================================
|
|
op.create_table(
|
|
'invoice_orders',
|
|
sa.Column('id', sa.String(32), primary_key=True),
|
|
sa.Column('invoice_id', sa.String(32), sa.ForeignKey('invoices.id', ondelete='CASCADE'), nullable=False),
|
|
sa.Column('order_id', sa.String(32), sa.ForeignKey('payment_orders.id', ondelete='CASCADE'), nullable=False),
|
|
sa.Column('order_no', sa.String(64), nullable=False),
|
|
sa.Column('amount', sa.Float, nullable=False, server_default='0'),
|
|
sa.Column('credits', sa.Float, nullable=False, server_default='0'),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
|
)
|
|
op.create_index('idx_invoice_orders_invoice', 'invoice_orders', ['invoice_id'])
|
|
op.create_index('idx_invoice_orders_order', 'invoice_orders', ['order_id'])
|
|
op.create_unique_constraint('uq_invoice_orders', 'invoice_orders', ['invoice_id', 'order_id'])
|
|
|
|
# ============================================================
|
|
# 3. 表注释和字段注释
|
|
# ============================================================
|
|
_comment_table('invoices', '发票主表')
|
|
_comment_column('invoices', 'id', '主键')
|
|
_comment_column('invoices', 'user_id', '申请用户ID')
|
|
_comment_column('invoices', 'invoice_no', '发票编号')
|
|
_comment_column('invoices', 'header_type', '抬头类型: personal/company')
|
|
_comment_column('invoices', 'header_name', '抬头名称')
|
|
_comment_column('invoices', 'header_tax_no', '税号')
|
|
_comment_column('invoices', 'header_register_address', '注册地址')
|
|
_comment_column('invoices', 'header_register_phone', '注册电话')
|
|
_comment_column('invoices', 'header_bank_name', '开户行')
|
|
_comment_column('invoices', 'header_bank_account', '银行账号')
|
|
_comment_column('invoices', 'email', '电子邮箱(必填)')
|
|
_comment_column('invoices', 'total_amount', '开票总金额')
|
|
_comment_column('invoices', 'total_credits', '总积分')
|
|
_comment_column('invoices', 'status', '状态: processing/success/failed')
|
|
_comment_column('invoices', 'failure_reason', '失败原因')
|
|
_comment_column('invoices', 'issued_at', '开票成功时间')
|
|
|
|
_comment_table('invoice_orders', '发票-订单关联表')
|
|
_comment_column('invoice_orders', 'id', '主键')
|
|
_comment_column('invoice_orders', 'invoice_id', '发票ID')
|
|
_comment_column('invoice_orders', 'order_id', '订单ID')
|
|
_comment_column('invoice_orders', 'order_no', '订单号(冗余)')
|
|
_comment_column('invoice_orders', 'amount', '订单金额(冗余)')
|
|
_comment_column('invoice_orders', 'credits', '订单积分(冗余)')
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_table('invoice_orders')
|
|
op.drop_table('invoices')
|