From 4b3797bfc87ef2b6be85ed025112b98f3b23ac31 Mon Sep 17 00:00:00 2001 From: shunli Date: Sat, 29 Aug 2026 21:17:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20r102=20=E6=99=AE=E9=80=9A?= =?UTF-8?q?=E6=8A=A5=E9=94=80=E7=BC=96=E8=BE=91=E5=A4=B9=E5=85=B7=EF=BC=88?= =?UTF-8?q?08-29-r102-reimbursement-fixture-debt=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../check.jsonl | 1 + .../implement.jsonl | 1 + .../implement.md | 18 +++++++ .../prd.md | 52 +++++++++++++++++++ .../task.json | 30 +++++++++++ .../e2e/r102-reimbursement-main-flow.spec.ts | 18 +++---- 6 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 .trellis/tasks/08-29-r102-reimbursement-fixture-debt/check.jsonl create mode 100644 .trellis/tasks/08-29-r102-reimbursement-fixture-debt/implement.jsonl create mode 100644 .trellis/tasks/08-29-r102-reimbursement-fixture-debt/implement.md create mode 100644 .trellis/tasks/08-29-r102-reimbursement-fixture-debt/prd.md create mode 100644 .trellis/tasks/08-29-r102-reimbursement-fixture-debt/task.json diff --git a/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/check.jsonl b/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/check.jsonl new file mode 100644 index 00000000..9dd3234a --- /dev/null +++ b/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/check.jsonl @@ -0,0 +1 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} diff --git a/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/implement.jsonl b/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/implement.jsonl new file mode 100644 index 00000000..9dd3234a --- /dev/null +++ b/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/implement.jsonl @@ -0,0 +1 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} diff --git a/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/implement.md b/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/implement.md new file mode 100644 index 00000000..67c348e6 --- /dev/null +++ b/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/implement.md @@ -0,0 +1,18 @@ +# 实施清单:修复 r102 普通报销编辑夹具 + +## 1. 夹具修正 + +- [x] 将目标用例整体改为语义一致的普通报销,不命中任何差旅识别条件。 +- [x] 保留编辑报销事由与日期、保存后展示更新的原始断言目标。 +- [x] 不修改生产组件、差旅识别规则或费用类型种子。 + +## 2. 验证 + +- [x] 完整 r102 从既有 5/6 恢复为 6/6 全绿。 +- [x] 核对改动仅限合成普通报销夹具,差旅识别/详情生产合同未改。 +- [x] 运行前端 typecheck、Trellis governance 和 `git diff --check`。 + +## 3. 收口 + +- [x] 只读检查 diff,确认没有通过改选择器或削弱断言制造假绿。 +- [ ] 中文提交信息引用 `08-29-r102-reimbursement-fixture-debt`。 diff --git a/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/prd.md b/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/prd.md new file mode 100644 index 00000000..a38027e4 --- /dev/null +++ b/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/prd.md @@ -0,0 +1,52 @@ +# 修复 r102 普通报销编辑夹具 + +## Goal + +恢复 r102 中“普通报销可编辑报销事由和日期”用例的真实业务前提,让回归重新验证普通报销头部编辑合同,而不是因夹具被识别成差旅报销后通过改选择器制造假绿。 + +## Confirmed Facts + +- `r102-reimbursement-main-flow.spec.ts` 的详情夹具把唯一明细费用类型设为 `category: "travel"`。 +- `isTravelReimbursement` 只要命中 `travelSummary.isTravel`、`linkedTravelRequestId` 或任一明细 `category === "travel"` 即判定为差旅。 +- 差旅详情向 `ReimbursementHeaderEditor` 传入 `showDescription=false`,按钮显示“编辑报销日期”,且“报销事由”输入框不渲染。 +- 既有失败与已上线的非参与人只读投影无关;只修改按钮选择器无法恢复用例原本要验证的行为。 + +## Scope + +- 仅调整 `r102-reimbursement-main-flow.spec.ts` 中普通报销编辑场景的合成夹具及必要断言。 +- 复跑完整 r102 和差旅识别相关聚焦回归,确认普通报销与差旅报销合同同时成立。 + +## Requirements + +- 把该用例的合成报销整体改为可信的普通报销场景,费用类型、事由、明细说明和预期文案必须语义一致。 +- 不通过只改 `category`、只改按钮名称或取消“报销事由”断言制造假绿。 +- 保留差旅识别规则和差旅详情不重复展示普通“报销事由”的现有产品合同。 +- 不修改生产业务代码、费用类型种子、权限、状态机或服务端 API。 + +## Acceptance Criteria + +- [x] 夹具不再命中 `isTravelReimbursement` 的任一条件,且内容能解释为普通报销。 +- [x] 用例仍点击“编辑”、修改“报销事由”和报销日期,并断言保存后的展示结果。 +- [x] 完整 `r102-reimbursement-main-flow.spec.ts` 通过,不再保留既有 5/6 基线。 +- [x] 差旅识别和差旅头部展示合同未改;完整 r102 中普通报销和管理员只读场景均通过。 +- [x] 前端 typecheck、相关 Playwright、Trellis governance 与 `git diff --check` 通过。 + +## Out of Scope + +- 调整 `isTravelReimbursement` 判定、差旅页面字段或按钮文案。 +- Task B 的费用类型重命名、票据关联和金额门禁。 +- 生产部署或生产数据写入。 + +## Test Plan + +- `cd client && npx playwright test tests/e2e/r102-reimbursement-main-flow.spec.ts --workers=1 --reporter=list` +- 运行现有差旅识别/详情聚焦测试;若仓库没有独立文件,至少由 r102 夹具断言普通报销路径,并核对相关既有套件。 +- `cd client && npm run typecheck` +- `node scripts/qa/check-trellis-governance.mjs` +- `git diff --check` + +## Validation Result + +- `npm run typecheck`:PASS。 +- `E2E_BASE_URL=http://127.0.0.1:5173 npx playwright test tests/e2e/r102-reimbursement-main-flow.spec.ts --workers=1 --reporter=list`:6/6 PASS。 +- 首次未启动本地服务的运行因 6 个 `ERR_CONNECTION_REFUSED` 失败;启动 Vite 后按相同测试命令复跑全绿,属于测试环境问题,不是产品断言失败。 diff --git a/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/task.json b/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/task.json new file mode 100644 index 00000000..72822f34 --- /dev/null +++ b/.trellis/tasks/08-29-r102-reimbursement-fixture-debt/task.json @@ -0,0 +1,30 @@ +{ + "id": "r102-reimbursement-fixture-debt", + "name": "r102-reimbursement-fixture-debt", + "title": "修复 r102 普通报销编辑夹具", + "description": "修复 r102 普通报销头部编辑用例误用 travel 类费用明细导致整个测试前提失效的问题。", + "status": "in_progress", + "dev_type": null, + "scope": "client Playwright reimbursement fixture and focused regression only", + "package": "client", + "priority": "P1", + "creator": "shunli", + "assignee": "shunli", + "createdAt": "2026-08-29", + "completedAt": null, + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [ + "client/tests/e2e/r102-reimbursement-main-flow.spec.ts", + "client/src/lib/reimbursementTravel.ts", + "client/src/components/reimbursements/ReimbursementHeaderEditor.tsx" + ], + "notes": "只修正合成夹具和测试断言,不改变差旅识别或生产页面行为。", + "meta": {} +} \ No newline at end of file diff --git a/client/tests/e2e/r102-reimbursement-main-flow.spec.ts b/client/tests/e2e/r102-reimbursement-main-flow.spec.ts index 64d5acd4..e77c6c6b 100644 --- a/client/tests/e2e/r102-reimbursement-main-flow.spec.ts +++ b/client/tests/e2e/r102-reimbursement-main-flow.spec.ts @@ -51,7 +51,7 @@ function reimbursementListItem() { status: "draft", reimbursementDate: "2026-05-06", totalAmount: "168.30", - description: "客户拜访交通费", + description: "办公室宽带费用", payment: { paymentVoucherNo: null, paymentDate: null, @@ -81,7 +81,7 @@ function reimbursementDetail(overrides: { reimbursementDate?: string; descriptio submittedAt: null, reimbursementDate: overrides.reimbursementDate ?? "2026-05-06", totalAmount: "168.30", - description: overrides.description ?? "客户拜访交通费", + description: overrides.description ?? "办公室宽带费用", projectId: null, linkedTravelRequestId: null, linkedTravelRequest: null, @@ -91,15 +91,15 @@ function reimbursementDetail(overrides: { reimbursementDate?: string; descriptio items: [ { id: itemId, - expenseTypeId: "expense-transport", + expenseTypeId: "expense-office-communication", expenseDate: "2026-05-06", amount: "168.30", - description: "客户拜访交通费", + description: "办公室宽带费用", expenseType: { - id: "expense-transport", - code: "transport", - name: "交通费", - category: "travel", + id: "expense-office-communication", + code: "office_communication", + name: "通讯费用", + category: "office", enabled: true, }, }, @@ -116,7 +116,7 @@ function reimbursementDetail(overrides: { reimbursementDate?: string; descriptio async function mockApis(page: Page) { let draftExists = true; let detailReimbursementDate = "2026-05-06"; - let detailDescription = "客户拜访交通费"; + let detailDescription = "办公室宽带费用"; await page.route((url) => url.pathname.startsWith("/api/"), async (route) => { const request = route.request();