feat(F.14): Unified Task System — F-TASK-MODEL/API/AGENT/WORK/UI/MIG/GOAL/TEST
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
- F-TASK-MODEL: Extended Task model with polymorphic assignee/entity/creator, subtasks, dependencies, task_type, success_criteria, progress - F-TASK-API: Extended task routes with polymorphic filters, subtasks, dependencies, new lifecycle - F-TASK-AGENT: ai_tools.py (191 lines) — create_task, assign_task, update_task_status, decompose_goal tools - F-TASK-WORK: workstream.py — task_card and goal_card blocks in communication system - F-TASK-UI: TaskBoard.tsx, TaskDetail.tsx, GoalView.tsx frontend components - F-TASK-MIG: Migration 0124 — new columns, data migration for contact_id/assigned_to - F-TASK-GOAL: Progress aggregation, success criteria evaluation, parent status propagation - F-TASK-TEST: test_unified_tasks.py (414 lines) - i18n updates for task system
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""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")
|
||||
Reference in New Issue
Block a user