"""任务系统 v2:发布字段扩展 + 交付/里程碑/验收/发票表 + 标书扩展 + 评价唯一约束 Revision ID: 0056 Revises: 0055 """ from alembic import op import sqlalchemy as sa revision = "0056" down_revision = "0055" branch_labels = None depends_on = None def upgrade() -> None: # ── tasks 扩展字段(0056)── op.add_column("tasks", sa.Column("resource_support_json", sa.Text(), nullable=False)) op.add_column("tasks", sa.Column("raw_materials_json", sa.Text(), nullable=False)) op.add_column("tasks", sa.Column("deliver_way", sa.String(length=20), nullable=False, server_default="online")) op.add_column("tasks", sa.Column("deliver_items", sa.Text(), nullable=False)) op.add_column("tasks", sa.Column("accept_standard", sa.Text(), nullable=False)) op.add_column("tasks", sa.Column("price_type", sa.String(length=20), nullable=False, server_default="fixed")) op.add_column("tasks", sa.Column("pay_type", sa.String(length=20), nullable=False, server_default="escrow")) op.add_column("tasks", sa.Column("escrow_ratio", sa.Integer(), nullable=False, server_default="100")) op.add_column("tasks", sa.Column("invoice_req", sa.Text(), nullable=False)) op.add_column("tasks", sa.Column("milestone_json", sa.Text(), nullable=False)) op.add_column("tasks", sa.Column("attachments_json", sa.Text(), nullable=False)) op.add_column("tasks", sa.Column("deliver_at", sa.String(length=40), nullable=False, server_default="")) op.add_column("tasks", sa.Column("accepted_at", sa.String(length=40), nullable=False, server_default="")) op.add_column("tasks", sa.Column("reject_count", sa.Integer(), nullable=False, server_default="0")) op.add_column("tasks", sa.Column("settle_status", sa.String(length=20), nullable=False, server_default="")) op.add_column("tasks", sa.Column("settled_at", sa.String(length=40), nullable=False, server_default="")) op.add_column("tasks", sa.Column("on_time", sa.Boolean(), nullable=True)) op.add_column("tasks", sa.Column("contract_status", sa.String(length=20), nullable=False, server_default="")) # ── bids 标书扩展 ── op.add_column("bids", sa.Column("deliver_days", sa.Integer(), nullable=False, server_default="0")) op.add_column("bids", sa.Column("team_json", sa.Text(), nullable=False)) op.add_column("bids", sa.Column("cases_json", sa.Text(), nullable=False)) op.add_column("bids", sa.Column("commit_json", sa.Text(), nullable=False)) op.add_column("bids", sa.Column("score_total", sa.Float(), nullable=False, server_default="0")) op.add_column("bids", sa.Column("score_json", sa.Text(), nullable=False)) op.add_column("bids", sa.Column("review_note", sa.String(length=500), nullable=False, server_default="")) # ── ratings:字段 + 唯一约束(先清理历史重复,保留最早一条)── op.add_column("ratings", sa.Column("rating_type", sa.String(length=20), nullable=False, server_default="fulfill")) op.add_column("ratings", sa.Column("dims_json", sa.Text(), nullable=False)) op.add_column("ratings", sa.Column("tags_json", sa.Text(), nullable=False)) op.execute( "DELETE r1 FROM ratings r1 " "INNER JOIN ratings r2 " "ON r1.task_id = r2.task_id AND r1.from_id = r2.from_id AND r1.id > r2.id" ) op.create_unique_constraint("uq_rating_task_from", "ratings", ["task_id", "from_id"]) # ── 新表:task_deliveries / task_milestones / task_acceptances / invoices ── op.create_table( "task_deliveries", sa.Column("id", sa.String(length=64), primary_key=True), sa.Column("task_id", sa.String(length=64), nullable=False, index=True), sa.Column("milestone_id", sa.String(length=64), nullable=False, server_default=""), sa.Column("version", sa.Integer(), nullable=False, server_default="1"), sa.Column("attachments_json", sa.Text(), nullable=False), sa.Column("note", sa.String(length=1000), nullable=False, server_default=""), sa.Column("created_at", sa.String(length=40), nullable=False, server_default=""), sa.ForeignKeyConstraint(["task_id"], ["tasks.id"], ondelete="CASCADE"), ) op.create_table( "task_milestones", sa.Column("id", sa.String(length=64), primary_key=True), sa.Column("task_id", sa.String(length=64), nullable=False, index=True), sa.Column("index", sa.Integer(), nullable=False, server_default="1"), sa.Column("name", sa.String(length=200), nullable=False, server_default=""), sa.Column("desc", sa.String(length=1000), nullable=False, server_default=""), sa.Column("days", sa.Integer(), nullable=False, server_default="0"), sa.Column("deliverables_json", sa.Text(), nullable=False), sa.Column("payment_ratio", sa.Float(), nullable=False, server_default="0"), sa.Column("status", sa.String(length=20), nullable=False, server_default="pending"), sa.Column("delivered_at", sa.String(length=40), nullable=False, server_default=""), sa.Column("accepted_at", sa.String(length=40), nullable=False, server_default=""), sa.Column("created_at", sa.String(length=40), nullable=False, server_default=""), sa.ForeignKeyConstraint(["task_id"], ["tasks.id"], ondelete="CASCADE"), ) op.create_table( "task_acceptances", sa.Column("id", sa.String(length=64), primary_key=True), sa.Column("task_id", sa.String(length=64), nullable=False, index=True), sa.Column("delivery_id", sa.String(length=64), nullable=False, server_default=""), sa.Column("milestone_id", sa.String(length=64), nullable=False, server_default=""), sa.Column("checklist_json", sa.Text(), nullable=False), sa.Column("score_json", sa.Text(), nullable=False), sa.Column("result", sa.String(length=20), nullable=False, server_default="pass"), sa.Column("comment", sa.String(length=1000), nullable=False, server_default=""), sa.Column("reviewer_id", sa.String(length=64), nullable=False, server_default=""), sa.Column("created_at", sa.String(length=40), nullable=False, server_default=""), sa.ForeignKeyConstraint(["task_id"], ["tasks.id"], ondelete="CASCADE"), ) op.create_table( "invoices", sa.Column("id", sa.String(length=64), primary_key=True), sa.Column("task_id", sa.String(length=64), nullable=False, index=True), sa.Column("escrow_id", sa.String(length=64), nullable=False, server_default=""), sa.Column("issuer_id", sa.String(length=64), nullable=False, server_default=""), sa.Column("issuer_name", sa.String(length=200), nullable=False, server_default=""), sa.Column("payer_name", sa.String(length=200), nullable=False, server_default=""), sa.Column("amount", sa.Integer(), nullable=False, server_default="0"), sa.Column("tax_rate", sa.Float(), nullable=False, server_default="0.01"), sa.Column("invoice_type", sa.String(length=20), nullable=False, server_default="personal"), sa.Column("status", sa.String(length=20), nullable=False, server_default="applied"), sa.Column("invoice_no", sa.String(length=64), nullable=False, server_default=""), sa.Column("created_at", sa.String(length=40), nullable=False, server_default=""), sa.Column("issued_at", sa.String(length=40), nullable=False, server_default=""), ) def downgrade() -> None: op.drop_table("invoices") op.drop_table("task_acceptances") op.drop_table("task_milestones") op.drop_table("task_deliveries") op.drop_constraint("uq_rating_task_from", "ratings", type_="unique") op.drop_column("ratings", "tags_json") op.drop_column("ratings", "dims_json") op.drop_column("ratings", "rating_type") op.drop_column("bids", "review_note") op.drop_column("bids", "score_json") op.drop_column("bids", "score_total") op.drop_column("bids", "commit_json") op.drop_column("bids", "cases_json") op.drop_column("bids", "team_json") op.drop_column("bids", "deliver_days") for col in ( "contract_status", "on_time", "settled_at", "settle_status", "reject_count", "accepted_at", "deliver_at", "attachments_json", "milestone_json", "invoice_req", "escrow_ratio", "pay_type", "price_type", "accept_standard", "deliver_items", "deliver_way", "raw_materials_json", "resource_support_json", ): op.drop_column("tasks", col)