fix(knowledge): 草稿的过程段写「收件箱 → 待办」,而不是两句「状态变更」
CI / backend (push) Canceled after 0s

自动生成的草稿里,时间线只查了 activity.action,没带 payload,
于是每条状态变化都变成一模一样的「状态变更」,读者看不出事情怎么走的。

- services/knowledge.snapshot_source 多取 Activity.payload 一起交给渲染
- domain/knowledge 加 STATUS_LABEL 与 _describe(),把 payload 的 from/to 翻成人话
- 新增用例断言草稿里有「待办 → 进行中」「进行中 → 已完成」

66 passed。
This commit is contained in:
dadao
2026-10-28 14:14:50 +08:00
parent 07688881b4
commit 0919c8ecd3
3 changed files with 61 additions and 3 deletions
+31 -1
View File
@@ -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():
+2 -2
View File
@@ -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
+28
View File
@@ -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