From 0919c8ecd3d6603ce8f1656e430db4a916c52055 Mon Sep 17 00:00:00 2001 From: dadao Date: Wed, 28 Oct 2026 14:14:50 +0800 Subject: [PATCH] =?UTF-8?q?fix(knowledge):=20=E8=8D=89=E7=A8=BF=E7=9A=84?= =?UTF-8?q?=E8=BF=87=E7=A8=8B=E6=AE=B5=E5=86=99=E3=80=8C=E6=94=B6=E4=BB=B6?= =?UTF-8?q?=E7=AE=B1=20=E2=86=92=20=E5=BE=85=E5=8A=9E=E3=80=8D=EF=BC=8C?= =?UTF-8?q?=E8=80=8C=E4=B8=8D=E6=98=AF=E4=B8=A4=E5=8F=A5=E3=80=8C=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=8F=98=E6=9B=B4=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自动生成的草稿里,时间线只查了 activity.action,没带 payload, 于是每条状态变化都变成一模一样的「状态变更」,读者看不出事情怎么走的。 - services/knowledge.snapshot_source 多取 Activity.payload 一起交给渲染 - domain/knowledge 加 STATUS_LABEL 与 _describe(),把 payload 的 from/to 翻成人话 - 新增用例断言草稿里有「待办 → 进行中」「进行中 → 已完成」 66 passed。 --- backend/app/domain/knowledge.py | 32 ++++++++++++++++++++++++++++++- backend/app/services/knowledge.py | 4 ++-- backend/tests/test_knowledge.py | 28 +++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/backend/app/domain/knowledge.py b/backend/app/domain/knowledge.py index 91484a9..aacf25c 100644 --- a/backend/app/domain/knowledge.py +++ b/backend/app/domain/knowledge.py @@ -53,6 +53,36 @@ ACTION_LABEL: dict[str, str] = { "knowledge_created": "沉淀为知识", } +# 状态流转在时间线里要写成人话:「待办 → 进行中」,光写"状态变更"等于没写 +STATUS_LABEL: dict[str, str] = { + "inbox": "收件箱", + "todo": "待办", + "doing": "进行中", + "blocked": "被阻塞", + "done": "已完成", + "canceled": "已取消", + "open": "待接收", + "accepted": "已接受", + "in_progress": "办理中", + "pending_verify": "待验收", +} + + +def _describe(row: dict[str, Any]) -> str: + """一条活动怎么念给读者听。""" + action = str(row.get("action") or "") + label = ACTION_LABEL[action] + payload = row.get("payload") or {} + if action == "status_changed" and isinstance(payload, dict): + src, dst = payload.get("from"), payload.get("to") + if src and dst: + from_label = STATUS_LABEL.get(str(src), str(src)) + to_label = STATUS_LABEL.get(str(dst), str(dst)) + return f"{from_label} → {to_label}" + if action == "knowledge_created" and isinstance(payload, dict) and payload.get("doc_type"): + return f"{label}({payload.get('doc_type')})" + return label + def target_status(action: str, current: str) -> str: """动作 → 目标状态。非法组合抛 7003,并把"现在还能做什么"带回去。""" @@ -160,7 +190,7 @@ def render_draft( out.append("## 过程") out.append("") for r in rows: - out.append(f"- {_fmt(r.get('at'))} {ACTION_LABEL[r['action']]}") + out.append(f"- {_fmt(r.get('at'))} {_describe(r)}") out.append("") if resolution_md.strip(): diff --git a/backend/app/services/knowledge.py b/backend/app/services/knowledge.py index c7c3830..aca2dd8 100644 --- a/backend/app/services/knowledge.py +++ b/backend/app/services/knowledge.py @@ -26,7 +26,7 @@ async def snapshot_source(session: AsyncSession, ws_id: uuid.UUID, item: Item) - ) timeline = ( await session.execute( - select(Activity.action, Activity.created_at) + select(Activity.action, Activity.created_at, Activity.payload) .where(Activity.workspace_id == ws_id, Activity.item_id == item.id) .order_by(Activity.created_at) ) @@ -47,7 +47,7 @@ async def snapshot_source(session: AsyncSession, ws_id: uuid.UUID, item: Item) - "提出人": nicknames.get(item.creator_id, ""), "办理人": nicknames.get(item.assignee_id, "") if item.assignee_id else "", }, - "timeline": [{"action": a, "at": at} for a, at in timeline], + "timeline": [{"action": a, "at": at, "payload": p or {}} for a, at, p in timeline], "comments": [ {"body": body, "author": nicknames.get(author_id, ""), "at": at} for body, author_id, at in comments diff --git a/backend/tests/test_knowledge.py b/backend/tests/test_knowledge.py index 230f157..44c8e6c 100644 --- a/backend/tests/test_knowledge.py +++ b/backend/tests/test_knowledge.py @@ -340,3 +340,31 @@ async def test_patch_uses_optimistic_lock(client: AsyncClient) -> None: ) assert stale.json()["code"] == 4009 assert stale.json()["data"]["current_version"] == updated["version"] + + +@pytest.mark.asyncio +async def test_draft_timeline_reads_like_a_story(client: AsyncClient) -> None: + """过程段要写「待办 → 已完成」,只写"状态变更"等于没写。 + + 这条用例是有来历的:时间线最初只查了 action,没带 payload, + 结果生成的草稿里全是"状态变更",看不出事情是怎么走的。 + """ + owner = await login(client) + ws = await create_workspace(client, owner, "时间线") + task = await _task(client, owner, ws["id"], "写清楚每一步") + for status in ("todo", "doing", "done"): + task = unwrap( + await client.post( + f"{V1}/workspaces/{ws['id']}/items/{task['id']}/status", + json={"status": status, "version": task["version"]}, + headers=owner["headers"], + ) + ) + doc = unwrap( + await client.post( + f"{V1}/workspaces/{ws['id']}/items/{task['id']}/knowledge", headers=owner["headers"] + ) + ) + body = doc["body_md"] + assert "待办 → 进行中" in body + assert "进行中 → 已完成" in body