diff --git a/pipeline_service/project_capability.py b/pipeline_service/project_capability.py index ff7fde7..dd2d2b3 100644 --- a/pipeline_service/project_capability.py +++ b/pipeline_service/project_capability.py @@ -216,6 +216,28 @@ async def start_project(project_id, who=None, agent_id=None): who=who, agent_id=agent_id) +async def _clear_project_todos(sor, project_id): + """删除项目全部待办:人类任务 + 冒泡问题(待办角标/列表的两个来源)。 + 关联链:project_id 直挂 / iteration_id 经迭代 / task_id 经任务 / tenant_id(questions)。 + 调用方保证在 sd_iterations / pipeline_tasks 删除之前执行(子查询依赖)。 + """ + if not project_id: + return + for sql in [ + "DELETE FROM pipeline_human_tasks WHERE project_id=${pid}$", + "DELETE FROM pipeline_human_tasks WHERE iteration_id IN (SELECT id FROM sd_iterations WHERE project_id=${pid}$)", + "DELETE FROM pipeline_human_tasks WHERE task_id IN (SELECT id FROM pipeline_tasks WHERE tenant_id=${pid}$)", + "DELETE FROM pipeline_agent_questions WHERE tenant_id=${pid}$", + "DELETE FROM pipeline_agent_questions WHERE task_id IN (SELECT id FROM pipeline_tasks WHERE tenant_id=${pid}$)", + ]: + try: + await sor.sqlExe(sql, {"pid": project_id}) + except Exception as e: + logger.warning("clear_project_todos failed: %s err=%s", sql, e) + await sor.sqlExe("COMMIT", {}) + logger.info("clear_project_todos: project=%s done", project_id) + + async def complete_project(project_id, who=None, agent_id=None): """完成项目:active → completed。""" return await _transition(project_id, S_ACTIVE, S_COMPLETED, 'complete', @@ -223,13 +245,18 @@ async def complete_project(project_id, who=None, agent_id=None): async def archive_project(project_id, who=None, agent_id=None): - """归档项目:completed → archived(也兼容 active → archived 强制归档)。""" + """归档项目:completed → archived(也兼容 active → archived 强制归档)。 + 归档成功后清空项目全部待办(2026-09-01)。""" ok, msg = await _transition(project_id, S_COMPLETED, S_ARCHIVED, 'archive', who=who, agent_id=agent_id) + if not ok: + ok, msg = await _transition(project_id, S_ACTIVE, S_ARCHIVED, 'archive', + who=who, agent_id=agent_id) if ok: - return ok, msg - return await _transition(project_id, S_ACTIVE, S_ARCHIVED, 'archive', - who=who, agent_id=agent_id) + db, dbname = _get_db() + async with db.sqlorContext(dbname) as sor: + await _clear_project_todos(sor, project_id) + return ok, msg async def reopen_project(project_id, who=None, agent_id=None): @@ -271,6 +298,8 @@ async def pause_project(project_id, who=None, agent_id=None): from_state=cur, to_state=S_PAUSED, who=_normalize_role(who), agent_id=agent_id, detail=f"prev_state={cur}", sor=sor) + # 停止推进后清空项目全部待办(2026-09-01):项目不再推进,遗留待办无意义且占角标 + await _clear_project_todos(sor, project_id) return True, S_PAUSED @@ -513,6 +542,10 @@ async def delete_project(project_id, who=None, agent_id=None, confirm=False): for sql in indirect: await sor.sqlExe(sql, {"pid": project_id}) + # 2a. 清空项目全部待办(2026-09-01):project_id 直挂 + iteration/task 关联。 + # 必须在 sd_iterations / pipeline_tasks 删除之前(子查询依赖)。 + await _clear_project_todos(sor, project_id) + # 2b. 任务关联表:先取本项目的任务 id,再删 task_id 指向它们的行 # (pipeline_deliverables 等同时有 project_id 和 task_id 两列,仅按 project_id # 删会漏掉「项目删除时 task 已先被删、project_id 已失效」的残留)