115 lines
5.2 KiB
Python
115 lines
5.2 KiB
Python
|
|
"""Unified Task System (F.14).
|
||
|
|
|
||
|
|
Adds polymorphic assignment/entity/creator fields, subtasks, dependencies,
|
||
|
|
goals/milestones and agent-subtask support to the tasks table. Migrates
|
||
|
|
legacy ``contact_id``/``assigned_to`` values into the polymorphic fields and
|
||
|
|
migrates existing ``agent_subtasks`` rows into tasks with
|
||
|
|
``task_type='agent_subtask'``.
|
||
|
|
|
||
|
|
Revision ID: 0124
|
||
|
|
Revises: 0123
|
||
|
|
"""
|
||
|
|
|
||
|
|
from alembic import op
|
||
|
|
import sqlalchemy as sa
|
||
|
|
from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID
|
||
|
|
|
||
|
|
revision = "0124"
|
||
|
|
down_revision = "0123"
|
||
|
|
branch_labels = None
|
||
|
|
depends_on = None
|
||
|
|
|
||
|
|
|
||
|
|
def upgrade() -> None:
|
||
|
|
# ── Add new columns to tasks ────────────────────────────────────────────
|
||
|
|
op.add_column("tasks", sa.Column("assignee_type", sa.String(20), nullable=False, server_default="user"))
|
||
|
|
op.add_column("tasks", sa.Column("assignee_id", PGUUID(as_uuid=True), nullable=True))
|
||
|
|
op.add_column("tasks", sa.Column("entity_type", sa.String(80), nullable=True))
|
||
|
|
op.add_column("tasks", sa.Column("entity_id", PGUUID(as_uuid=True), nullable=True))
|
||
|
|
op.add_column("tasks", sa.Column("creator_type", sa.String(20), nullable=False, server_default="user"))
|
||
|
|
op.add_column("tasks", sa.Column("creator_id", PGUUID(as_uuid=True), nullable=True))
|
||
|
|
op.add_column("tasks", sa.Column("parent_task_id", PGUUID(as_uuid=True), nullable=True))
|
||
|
|
op.add_column("tasks", sa.Column("depends_on", JSONB, nullable=False, server_default=sa.text("'[]'::jsonb")))
|
||
|
|
op.add_column("tasks", sa.Column("task_type", sa.String(30), nullable=False, server_default="todo"))
|
||
|
|
op.add_column("tasks", sa.Column("success_criteria", JSONB, nullable=True))
|
||
|
|
op.add_column("tasks", sa.Column("target_date", sa.DateTime(timezone=True), nullable=True))
|
||
|
|
op.add_column("tasks", sa.Column("progress", sa.Integer(), nullable=False, server_default="0"))
|
||
|
|
|
||
|
|
# ── Migrate legacy data into polymorphic fields ─────────────────────────
|
||
|
|
# contact_id → entity_type='contact' + entity_id
|
||
|
|
op.execute(
|
||
|
|
"""
|
||
|
|
UPDATE tasks
|
||
|
|
SET entity_type = 'contact', entity_id = contact_id
|
||
|
|
WHERE contact_id IS NOT NULL AND entity_type IS NULL
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
# assigned_to → assignee_type='user' + assignee_id
|
||
|
|
op.execute(
|
||
|
|
"""
|
||
|
|
UPDATE tasks
|
||
|
|
SET assignee_type = 'user', assignee_id = assigned_to
|
||
|
|
WHERE assigned_to IS NOT NULL AND assignee_id IS NULL
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
# created_by → creator_type='user' + creator_id
|
||
|
|
op.execute(
|
||
|
|
"""
|
||
|
|
UPDATE tasks
|
||
|
|
SET creator_type = 'user', creator_id = created_by
|
||
|
|
WHERE created_by IS NOT NULL AND creator_id IS NULL
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
|
||
|
|
# ── Migrate AgentSubtask rows into tasks ────────────────────────────────
|
||
|
|
op.execute(
|
||
|
|
"""
|
||
|
|
INSERT INTO tasks (
|
||
|
|
id, tenant_id, title, description, status, priority,
|
||
|
|
assignee_type, assignee_id, entity_type, entity_id,
|
||
|
|
creator_type, creator_id, task_type, depends_on, progress,
|
||
|
|
created_at, updated_at
|
||
|
|
)
|
||
|
|
SELECT
|
||
|
|
asub.id, asub.tenant_id,
|
||
|
|
asub.task_description, asub.task_description, asub.status, 'medium',
|
||
|
|
'agent', asub.child_agent_id, 'agent', asub.parent_agent_id,
|
||
|
|
'agent', asub.parent_agent_id, 'agent_subtask', '[]'::jsonb, 0,
|
||
|
|
asub.created_at, asub.updated_at
|
||
|
|
FROM agent_subtasks asub
|
||
|
|
WHERE NOT EXISTS (
|
||
|
|
SELECT 1 FROM tasks t WHERE t.id = asub.id
|
||
|
|
)
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
|
||
|
|
# ── Indexes ─────────────────────────────────────────────────────────────
|
||
|
|
op.create_index("ix_tasks_tenant_entity", "tasks", ["tenant_id", "entity_type", "entity_id"])
|
||
|
|
op.create_index("ix_tasks_tenant_assignee", "tasks", ["tenant_id", "assignee_type", "assignee_id"])
|
||
|
|
op.create_index("ix_tasks_tenant_parent", "tasks", ["tenant_id", "parent_task_id"])
|
||
|
|
op.create_index("ix_tasks_tenant_type", "tasks", ["tenant_id", "task_type"])
|
||
|
|
op.create_foreign_key(
|
||
|
|
"fk_tasks_parent_task_id", "tasks", "tasks", ["parent_task_id"], ["id"],
|
||
|
|
ondelete="CASCADE",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def downgrade() -> None:
|
||
|
|
op.drop_constraint("fk_tasks_parent_task_id", "tasks", type_="foreignkey")
|
||
|
|
op.drop_index("ix_tasks_tenant_type", table_name="tasks")
|
||
|
|
op.drop_index("ix_tasks_tenant_parent", table_name="tasks")
|
||
|
|
op.drop_index("ix_tasks_tenant_assignee", table_name="tasks")
|
||
|
|
op.drop_index("ix_tasks_tenant_entity", table_name="tasks")
|
||
|
|
op.drop_column("tasks", "progress")
|
||
|
|
op.drop_column("tasks", "target_date")
|
||
|
|
op.drop_column("tasks", "success_criteria")
|
||
|
|
op.drop_column("tasks", "task_type")
|
||
|
|
op.drop_column("tasks", "depends_on")
|
||
|
|
op.drop_column("tasks", "parent_task_id")
|
||
|
|
op.drop_column("tasks", "creator_id")
|
||
|
|
op.drop_column("tasks", "creator_type")
|
||
|
|
op.drop_column("tasks", "entity_id")
|
||
|
|
op.drop_column("tasks", "entity_type")
|
||
|
|
op.drop_column("tasks", "assignee_id")
|
||
|
|
op.drop_column("tasks", "assignee_type")
|