Files
crm_project/server/alembic/versions/20260327_phase_b_contract_management.py
T
hankin 815cbf9d8c v0.2.0: CRM/ERP 系统升级 - 清理 .gitignore 并移除误提交的 venv/env/db 文件
- 更新 .gitignore:全面覆盖环境变量、数据库、日志、缓存、上传文件
- 移除误跟踪的 server/venv/、crm_data.db、.env 文件
- 新增 server/.env.example 模板
- 新增合同管理、利润核算、AI教练等功能模块
- 新增 Playwright e2e 测试套件
- 前后端多项功能升级和 bug 修复
2026-05-11 07:24:19 +00:00

114 lines
5.3 KiB
Python

"""Phase B: contract management + order/invoice linkage
Revision ID: e5f6a7b8c9d0
Revises: d4e5f6a7b8c9
Create Date: 2026-03-27
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import UUID, JSONB
revision = "e5f6a7b8c9d0"
down_revision = "d4e5f6a7b8c9"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ── 1. sys_companies 新增 full_info ──
op.add_column(
"sys_companies",
sa.Column("full_info", JSONB, nullable=True,
comment="公司完整信息: full_name/address/phone/bank_name/bank_account/tax_id"),
)
# ── 2. erp_contracts 主表 ──
op.create_table(
"erp_contracts",
sa.Column("id", UUID(as_uuid=True), primary_key=True),
sa.Column("contract_no", sa.String(30), unique=True, nullable=False),
sa.Column("buyer_customer_id", UUID(as_uuid=True), sa.ForeignKey("crm_customers.id"), nullable=False),
sa.Column("seller_company_id", UUID(as_uuid=True), sa.ForeignKey("sys_companies.id"), nullable=False),
sa.Column("company_id", UUID(as_uuid=True), sa.ForeignKey("sys_companies.id"), nullable=False, index=True),
sa.Column("total_amount_excl_tax", sa.Numeric(14, 2), default=0),
sa.Column("total_amount_incl_tax", sa.Numeric(14, 2), default=0),
sa.Column("total_amount_cn", sa.String(100), nullable=True),
sa.Column("payment_terms", sa.String(50), nullable=False, server_default="货到付全款"),
sa.Column("shipping_terms", sa.String(50), nullable=False, server_default="买方自提"),
sa.Column("status", sa.String(20), nullable=False, server_default="draft"),
sa.Column("is_signed", sa.Boolean, default=False, server_default="false"),
sa.Column("signed_file_url", sa.String(500), nullable=True),
sa.Column("linked_order_id", UUID(as_uuid=True), sa.ForeignKey("erp_orders.id"), nullable=True),
sa.Column("salesperson_id", UUID(as_uuid=True), sa.ForeignKey("sys_users.id"), nullable=True),
sa.Column("sign_date", sa.Date, nullable=True),
sa.Column("remark", sa.Text, nullable=True),
sa.Column("created_at", sa.DateTime, server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime, server_default=sa.func.now()),
sa.Column("is_deleted", sa.Boolean, default=False, server_default="false"),
)
# ── 3. erp_contract_items 明细行 ──
op.create_table(
"erp_contract_items",
sa.Column("id", UUID(as_uuid=True), primary_key=True),
sa.Column("contract_id", UUID(as_uuid=True), sa.ForeignKey("erp_contracts.id"), nullable=False),
sa.Column("sku_id", UUID(as_uuid=True), sa.ForeignKey("erp_product_skus.id"), nullable=False),
sa.Column("qty", sa.Numeric(12, 2), nullable=False),
sa.Column("unit_price", sa.Numeric(12, 2), nullable=False),
sa.Column("sub_total", sa.Numeric(14, 2), nullable=False),
sa.Column("created_at", sa.DateTime, server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime, server_default=sa.func.now()),
sa.Column("is_deleted", sa.Boolean, default=False, server_default="false"),
)
# ── 4. erp_contract_attachments 附件 ──
op.create_table(
"erp_contract_attachments",
sa.Column("id", UUID(as_uuid=True), primary_key=True),
sa.Column("contract_id", UUID(as_uuid=True), sa.ForeignKey("erp_contracts.id"), nullable=False),
sa.Column("file_name", sa.String(200), nullable=False),
sa.Column("file_url", sa.String(500), nullable=False),
sa.Column("file_type", sa.String(30), nullable=False, server_default="signed_copy"),
sa.Column("uploader_id", UUID(as_uuid=True), sa.ForeignKey("sys_users.id"), nullable=True),
sa.Column("created_at", sa.DateTime, server_default=sa.func.now()),
sa.Column("is_deleted", sa.Boolean, default=False, server_default="false"),
)
# ── 5. erp_orders 新增 contract_id ──
op.add_column(
"erp_orders",
sa.Column("contract_id", UUID(as_uuid=True),
sa.ForeignKey("erp_contracts.id"), nullable=True,
comment="来源合同(一键推单后回填)"),
)
# ── 6. finance_sales_invoices 新增 order_id / shipping_record_id / payment_due_date ──
op.add_column(
"finance_sales_invoices",
sa.Column("order_id", UUID(as_uuid=True),
sa.ForeignKey("erp_orders.id"), nullable=True,
comment="关联订单"),
)
op.add_column(
"finance_sales_invoices",
sa.Column("shipping_record_id", UUID(as_uuid=True),
sa.ForeignKey("erp_shipping_records.id"), nullable=True,
comment="关联发货单"),
)
op.add_column(
"finance_sales_invoices",
sa.Column("payment_due_date", sa.Date, nullable=True,
comment="回款截止日(根据合同付款条件自动推算)"),
)
def downgrade() -> None:
op.drop_column("finance_sales_invoices", "payment_due_date")
op.drop_column("finance_sales_invoices", "shipping_record_id")
op.drop_column("finance_sales_invoices", "order_id")
op.drop_column("erp_orders", "contract_id")
op.drop_table("erp_contract_attachments")
op.drop_table("erp_contract_items")
op.drop_table("erp_contracts")
op.drop_column("sys_companies", "full_info")