WF8-10 移动审批展示处理历史
这个提交包含在:
@@ -143,6 +143,27 @@ function workflowStatusLabel(status: string) {
|
||||
return status;
|
||||
}
|
||||
|
||||
function workflowActionLabel(action: string | null | undefined) {
|
||||
if (!action) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
const labels: Record<string, string> = {
|
||||
submit: "提交",
|
||||
approve: "通过",
|
||||
reject: "驳回",
|
||||
return: "退回",
|
||||
transfer: "转审",
|
||||
withdraw: "撤回",
|
||||
remind: "催办",
|
||||
overdue_remind: "超时提醒",
|
||||
cancel: "取消",
|
||||
skip: "跳过",
|
||||
};
|
||||
|
||||
return labels[action] ?? action;
|
||||
}
|
||||
|
||||
function businessStatusLabel(status: string | null | undefined) {
|
||||
const labels: Record<string, string> = {
|
||||
draft: "草稿",
|
||||
@@ -1322,6 +1343,26 @@ export function MobileApprovalsPage() {
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2" data-testid="mobile-approvals-history">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h4 className="text-sm font-semibold text-zinc-900">处理历史</h4>
|
||||
<span className="text-xs text-zinc-500">{selectedTask.history.length} 条</span>
|
||||
</div>
|
||||
{selectedTask.history.length ? (
|
||||
selectedTask.history.map((item) => (
|
||||
<article className="mobile-list-card text-sm" data-testid="mobile-approvals-history-item" key={item.id}>
|
||||
<div className="font-medium text-zinc-900">
|
||||
{workflowActionLabel(item.action)} · {item.actorName || item.actorEmployeeNo || item.actorId}
|
||||
</div>
|
||||
<div className="mt-1 text-zinc-600">{item.comment || "-"}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">{formatDateTime(item.createdAt)}</div>
|
||||
</article>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-zinc-500">暂无历史记录</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!selectedTaskAllowsActions ? (
|
||||
<p className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-sm text-zinc-600">
|
||||
当前任务已处理,仅供查看,不支持再次审批。
|
||||
|
||||
@@ -272,6 +272,31 @@ function workflowTaskSummary(status: "pending" | "completed" = "pending") {
|
||||
|
||||
function workflowTaskDetail(status: "pending" | "completed" = "pending") {
|
||||
const isCompleted = status === "completed";
|
||||
const history = [
|
||||
{
|
||||
id: "history-workflow-submit-dg7",
|
||||
taskId: null,
|
||||
actorId: "employee-dg7-employee",
|
||||
actorName: "测试员工",
|
||||
actorEmployeeNo: "E-DG7",
|
||||
action: "submit",
|
||||
comment: "提交审批",
|
||||
payload: null,
|
||||
createdAt: "2026-05-12T08:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: isCompleted ? "history-workflow-approve-dg7" : "history-workflow-transfer-dg7",
|
||||
taskId: isCompleted ? "task-dg7-completed" : "task-dg7",
|
||||
actorId: "employee-dg7-manager",
|
||||
actorName: "测试审批人",
|
||||
actorEmployeeNo: "M-DG7",
|
||||
action: isCompleted ? "approve" : "transfer",
|
||||
comment: isCompleted ? "同意报销" : "转给财务复核",
|
||||
payload: isCompleted ? null : { transferToEmployeeId: "employee-dg7-finance" },
|
||||
createdAt: isCompleted ? "2026-05-12T09:00:00.000Z" : "2026-05-12T08:30:00.000Z",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
task: {
|
||||
id: isCompleted ? "task-dg7-completed" : "task-dg7",
|
||||
@@ -309,7 +334,7 @@ function workflowTaskDetail(status: "pending" | "completed" = "pending") {
|
||||
purchaseRequest: null,
|
||||
travelRequest: null,
|
||||
allowedActions: isCompleted ? [] : ["approve", "reject", "return", "transfer"],
|
||||
history: [],
|
||||
history,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -874,6 +899,9 @@ test.describe("MIF7 移动信息架构整改", () => {
|
||||
await expect(mobile.page.getByTestId("mobile-bottom-nav")).toHaveCount(0);
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-detail")).not.toContainText("业务单号");
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-detail")).toContainText("报销单 BX20260512001");
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-history")).toContainText("提交 · 测试员工");
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-history")).toContainText("转审 · 测试审批人");
|
||||
await expect(mobile.page.getByTestId("mobile-approvals-history")).not.toContainText("transferToEmployeeId");
|
||||
await expectNoMobileShellRegression(mobile.page);
|
||||
await mobile.page.screenshot({ path: screenshotPath("manager-approval-push-detail"), fullPage: true });
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ WF8-6,通知失败可追溯,"补强企业微信、站内和可选邮件通知失
|
||||
WF8-7,邮件通道决策记录,"决定邮件通道是否进入当前验收,若不进入则冻结并说明替代通知通道。","1) 邮件是否验收有明确结论;2) 不影响企业微信和站内通知验收;3) 冻结原因可追溯;4) 后续解锁条件清楚。","流程级验证:人工核对需求规约和 Issue Notes 中邮件通道决策。",manual,DONE,DONE,DONE,docs/需求-统一审批中心规约.md|issues/2026-05-13_18-23-52-wf8-approval-notification.csv,"[""WF8-1""]",邮件通道当前冻结,不作为 WF8 硬门禁;站内通知和企业微信个人应用消息继续验收;解锁条件已写入规约和本 Notes。
|
||||
WF8-8,三渠道通知验收设计,"设计站内、企业微信个人应用消息和邮件或冻结替代通道的三渠道验收口径。","1) 三渠道或二渠道加冻结说明清楚;2) deeplink 回 OA;3) 缺配置时 SKIP 不等于 PASS;4) 真机和公网边界明确。","流程级验证:人工审查通知矩阵;必要时执行 live readiness 和企业微信真机验证。",manual|bash|playwright,DONE,DONE,DONE,docs/试运行与自动化验证手册.md|docs/需求-企业微信基础设施规约.md,"[""WF8-6"",""WF8-7""]",Runbook 已新增 WF8 通知渠道验收矩阵:站内/企业微信个人应用/邮件冻结/失败追溯四类口径;明确本地自动化/公网匿名/公网带凭据/企业微信真机证据边界;rg Test_Method 已通过;本任务仅设计验收口径 未执行真机验收。
|
||||
WF8-9,催办和超时提醒回归,"回归既有催办和超时提醒,避免转审或通知增强引入倒退。","1) 催办不改变审批状态;2) 超时提醒不重复刷屏;3) 通知对象正确;4) 历史记录可追溯。","流程级验证:创建待审批任务 -> 触发催办或超时提醒 -> 检查通知和 workflow history。",node|bash|manual,DONE,DONE,DONE,server/src|server/scripts|docs/需求-统一审批中心规约.md,"[""WF8-6""]",AP6 后提醒能力是当前事实;已跑 E2E AP6 审批催办与待办汇总 1 passed 覆盖申请人催办/经理和 admin 禁止/重复 24h 限制/history/notification/audit/summary;已跑 workflow.travel、workflowOverdueReminderJob、notificationTypes 单测 7 passed 覆盖超时提醒 job 和通知类型;node --check workflow.js/workflowOverdueReminderJob.js 通过。
|
||||
WF8-10,审批历史一致性,"确保转审、催办、通过、驳回和通知事件在审批历史中一致展示。","1) 历史顺序清楚;2) 操作人和目标人可解释;3) 不泄露敏感字段;4) PC 和移动展示一致。","流程级验证:提交审批 -> 转审 -> 催办 -> 审批处理 -> PC 和移动查看历史。",playwright|node|manual,TODO,TODO,TODO,server/src|client/src,"[""WF8-3"",""WF8-4"",""WF8-5"",""WF8-9""]",待批准方案:PC WorkflowTaskDetailPage 已展示 detail.history;MobileApprovalsPage 已获取同一 fetchWorkflowTaskDetail 结果但 push detail 未渲染 selectedTask.history;批准后仅改 client/src/pages/MobileApprovalsPage.tsx 新增移动处理历史卡片和 workflowActionLabel 映射 展示动作 操作人 意见 时间 空态;不改后端 不改数据结构 不引入新依赖;验证 client typecheck/build 和 PC/移动审批历史一致性 Playwright。
|
||||
WF8-10,审批历史一致性,"确保转审、催办、通过、驳回和通知事件在审批历史中一致展示。","1) 历史顺序清楚;2) 操作人和目标人可解释;3) 不泄露敏感字段;4) PC 和移动展示一致。","流程级验证:提交审批 -> 转审 -> 催办 -> 审批处理 -> PC 和移动查看历史。",playwright|node|manual,DONE,DONE,DONE,server/src|client/src,"[""WF8-3"",""WF8-4"",""WF8-5"",""WF8-9""]",完成:MobileApprovalsPage 渲染 selectedTask.history 的移动处理历史卡片并复用动作中文映射;仅展示动作 操作人 意见 时间和空态 不渲染 payload;不改后端 数据结构 权限或状态机;验证 npm run typecheck 通过;npm run build 通过;npx playwright test tests/e2e/dg7-mobile-information-dedupe.spec.ts --workers=1 --reporter=list 在启动本地 3000 服务后 5 passed。
|
||||
WF8-11,通知 deeplink 回 OA 验证,"验证企业微信个人应用消息或站内通知 deeplink 能回到 OA 目标页面。","1) deeplink 指向具体业务目标;2) 不只返回通知中心;3) 未登录或未绑定有可解释提示;4) 移动布局正常。","流程级验证:触发通知 -> 点击 deeplink -> 企业微信或移动浏览器打开 OA 目标详情 -> 检查权限和页面。",playwright|manual,TODO,TODO,TODO,client/src|server/src|docs/trialrun/真机公网测试清单.md,"[""WF8-8"",""WF8-10""]",真机验收不能被自动化替代。
|
||||
WF8-12,workflow reviewer 审查,"对 WF8 涉及的 workflow 状态、权限和通知副作用做独立 review。","1) 非法状态跳转无阻断;2) 权限泄漏无阻断;3) 重复审批和并发风险可控;4) 漏测项有归属。","流程级验证:reviewer 人工审查 diff、Issue CSV 和测试证据。",manual,TODO,TODO,TODO,issues/2026-05-13_18-23-52-wf8-approval-notification.csv|server/src|client/src,"[""WF8-10"",""WF8-11""]",实现后立即 review。
|
||||
WF8-13,WF8 收口准备,"整理 WF8 验证摘要、通知矩阵、未完成项和后续业务影响。","1) DONE 行均有证据;2) 通知渠道结论清楚;3) 未完成项不伪装完成;4) 文档入口同步。","流程级验证:人工核对 WF8 CSV 状态、验证摘要、通知矩阵和 git diff。",manual|git,TODO,TODO,TODO,issues/2026-05-13_18-23-52-wf8-approval-notification.csv|docs/文档索引.md,"[""WF8-12""]",对应原规划 WF8-13。
|
||||
|
||||
|
在新工单中引用
屏蔽一个用户