"""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")