chore(08-05-business-detail-routing): 归档任务

这个提交包含在:
2026-08-07 11:02:26 +08:00
父节点 7c44e05ad6
当前提交 0124a2be98
共修改 6 个文件,包含 3 行新增和 3 行删除
@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. 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."}
@@ -0,0 +1,84 @@
# 设计说明
## 1. 架构边界与目标路由
本任务只迁移采购、资产已有详情/编辑能力,并为“提交前没有实体 ID”的资产创建表单提供独立路由。列表数据、详情 API、mutation payload、权限数据源、审计和状态机保持原合同,不新增后端 endpoint 或数据库字段。
| 模块 | 列表 | 创建 | 详情 |
| --- | --- | --- | --- |
| 采购 | `/purchase-requests` | 不新增;列表先创建草稿 | `/purchase-requests/:purchaseRequestId` |
| 资产 | `/assets` | `/assets/new` | `/assets/:assetId` |
路由注册与页面元数据必须把 `/assets/new` 作为静态路径处理,不能落入 `:assetId`。`PAGE_TITLES` 增加采购详情、资产新建、资产详情层级,导航仍按父模块高亮。
## 2. 页面与数据职责拆分
- `PurchaseRequestsPage` 收敛为采购列表页:持有 URL 筛选、分页、列表 query、创建草稿入口和行级详情入口。
- `PurchaseRequestDetailPage` 承接现有采购 detail query、表单 hydration、附件、采购单归档、提交/撤回、审计/状态动作和 dirty 状态;不得为渲染详情请求完整采购列表。
- `AssetRegistryPage` 收敛为资产列表页:持有 URL 筛选、资产列表 query、资产分类管理、移动扫码入口、创建入口和行级详情入口。
- `AssetDetailPage` 与 `AssetCreatePage` 使用同一资产编辑工作区/section 集合:详情模式加载实体并按现有权限编辑与流转;创建模式使用初始表单并只执行 POST,避免复制两份长表单。
- 列表页不再加载详情专用 options;详情/创建页只加载自身表单所需的分类、公司、成本中心、候选采购等数据。React Query 可复用缓存,但职责不能靠隐藏 JSX 维持。
- 优先提取页面内类型、纯函数和有明确领域含义的 section;不建立跨采购/资产的“万能详情页”。
## 3. 列表筛选 URL 合同
筛选参数由 URL 直接派生,避免“URL 一份、本地 state 一份”的双向同步 effect。筛选输入使用 `replace` 更新当前列表 history entry;采购翻页同样保留当前筛选。资产文本搜索维持现有即时查询体验,但用 deferred/debounce 值触发请求,避免每次键入造成不必要的渲染/请求抖动。
| 模块 | 查询参数 | 默认与约束 |
| --- | --- | --- |
| 采购 | `status`、`scope`、`supplierId`、`legalEntityId`、`costCenterId`、`page` | 无 `status` 时为 `draft`;`status=all` 表示全部;无 `scope` 时为 `own`;`scope=all` 仅 `canViewAll` 生效;`page` 只接受正整数 |
| 资产 | `q`、`status`、`assetType`、`sensitivity`、`categoryId`、`legalEntityId`、`costCenterId`、`assigneeId`、`scope` | 无 `scope` 时保留现有按权限决定的默认范围;显式 `own/all` 仅在有全量权限时可切换,否则强制 own |
联动筛选一次性更新:公司变化且原成本中心不再属于该公司时,同一次 replace 删除 `costCenterId`;采购非页码筛选变化同时把 `page` 归一到 1。未知枚举、无效页码或越权范围只在读取层回落,不进入 API 查询条件。
## 4. 导航、返回与创建完成
- 列表进入详情使用 history push,并把 `pathname + search` 作为 `location.state.from`;浏览器后退自然回到原列表 entry,显式返回使用经校验的来源。
- 扩展 `pcDetailNavigation` 为显式 allowlist 合同,不把任意字符串当 return URL。采购详情允许采购列表来源以及受控的项目详情来源;资产详情/创建只允许资产列表来源。绝对 URL、协议相对 URL、hash、当前页和其他模块来源全部拒绝。
- 无安全来源时,采购详情回到 `/purchase-requests`,资产详情/创建回到 `/assets`。从全局搜索或通知进入时,浏览器后退仍按 history 返回原页面,显式返回使用安全列表兜底。
- 采购创建保留现有“先 POST 空草稿”合同;成功后 push 到采购详情并携带当前列表来源。
- 资产列表的“新增资产”push 到 `/assets/new` 并携带列表来源。创建成功后使用 replace 进入 `/assets/:assetId`,继续携带原列表来源,避免返回到已提交的创建表单。
- 项目详情中的采购链接携带项目详情作为安全来源;详情返回文案按来源显示“返回项目详情”或“返回采购列表”。
## 5. 路径构造与现有入口
- 新增单一的采购/资产详情路径构造函数,对实体 ID 使用路径段编码;列表、创建成功、项目关联、通知和搜索解析都消费该函数,避免分散拼接。
- `GlobalSearchDialog` 对 `purchase_request` 与 `asset` 使用 `entityType + entityId` 构造 canonical 路径,不继续信任旧服务端 `href` 的 `?id=` 或列表级值;其他实体仍沿用原合同。本任务因此无需修改全局搜索 API shape。
- PC `notificationLinks` 将 `purchase_request`、`asset`/`asset_card` 直接解析为新详情路径,停止生成未消费的 `highlight`;移动通知解析保持现有移动路由边界。
- `ProjectDetailPage` 的采购关联改为新详情路径;没有现有入口的模块不为满足“路由统一”额外新增按钮。
- `client/src` 中旧 `?id=` 只允许出现在明确命名的 legacy redirect/parser 与兼容测试中。服务端历史集成审计字段或未被前端消费的 routePath 不在本任务内,不能借机改 API 或集成逻辑。
## 6. 旧链接兼容
- `/purchase-requests?id=<id>` 与 `/assets?id=<id>` 在对应列表入口、列表 query 启动前解析;有非空 ID 时 replace 到 canonical 详情路径,避免先闪现列表或同时请求列表/详情。
- redirect 从查询参数中删除 `id`,将剩余且仍属于对应列表的参数归一化后写入安全 `state.from`;canonical 详情 URL 不携带列表筛选噪音。
- 空 ID、只有空白字符或缺少 ID 时不重定向,列表按其余有效筛选正常展示。未知实体 ID 进入详情后的统一错误态。
- redirect 使用 replace,历史栈不保留旧 URL;仓库内新入口全部使用 canonical 路径,旧格式仅作为外部收藏、旧 witness 和历史链接兼容层。
## 7. Dirty guard、错误态与权限
- 采购详情、资产详情和资产创建页面继续注册 `useUnsavedChangesGuard`;显式返回、浏览器后退、跨模块链接和直接路由离开均由现有 provider 阻断。
- 用户取消离开时不执行 navigate,保留当前 URL、实体和全部输入;确认后才执行原导航意图。资产创建成功后的 replace 只在 mutation 成功后发生。
- 403/404 统一映射为普通中文安全错误态:“无法查看该记录,记录可能不存在或你无权访问”;不渲染已取得的局部摘要,不区分可用于资源枚举的原因。
- 采购列表与详情沿用原 `RequirePermission` anyOf;资产列表与详情沿用原资产 anyOf。`/assets/new` 单独要求 `asset.manage`,页面内编辑、流转、敏感字段和附件权限继续使用现有细粒度判断。
- API endpoint、请求 payload、审计、高风险确认、附件下载原因和状态矩阵均不改变。
## 8. 移动兼容
- 现有 `/mobile/purchase-requests/:id` 不变;`runtimeContext` 继续把 PC 采购详情映射到对应移动详情。
- 本任务不新增移动资产详情或创建能力。移动工作台偏好环境访问 `/assets`、`/assets/new` 或 `/assets/:assetId` 时统一回落到既有 `/mobile/assets/scan`,保持旧 `/assets?id=` 在移动端的能力边界。
- 新 PC 详情路由不得改变移动底部导航、企业微信入口或资产扫码/盘点流程。
## 9. 测试策略
- 为两个模块建立同构路由合同:列表不发详情 query、详情不发完整列表 query、列表进入、直接深链、刷新、返回恢复、旧链 replace、无效/无权限、dirty 取消/确认。
- 单独覆盖资产 `/assets/new`:静态路由优先、`asset.manage` guard、直接访问、取消返回、创建成功 replace、失败保留输入、成功后后退不回创建页。
- 覆盖 URL 筛选事实源:直接打开筛选 URL、筛选 replace、采购 page 重置/恢复、范围权限收紧、公司/成本中心联动归一。
- 覆盖项目关联、全局搜索、PC 通知的 canonical URL;全局搜索 purchase/asset 不受服务端旧 `href` 影响。
- 更新 `procurement-flow.spec.ts`、`assets-flow.spec.ts`、`r103-cross-page-ui-consistency.spec.ts`、`pc-detail-mobile-visual-wave3.spec.ts`、`global-search.spec.ts` 及通知/移动路由用例,必要时增加专门的 `business-detail-routing.spec.ts`。
- 测试使用完整 URL 与页面 marker 断言,不用父列表路径前缀误认动态详情;403 与 404 都断言同一安全文案。
## 10. 回滚
- 新路由、页面拆分、筛选 URL、入口消费者和 legacy redirect 作为同一任务提交;回滚时整体恢复 `?id=` 页面形态,避免消费者指向不存在的页面。
- 无 schema、API 或数据迁移。应用回滚不处理数据库;若需部分回滚,必须至少同时回滚 route registry、lazy exports、canonical path builder 和所有前端消费者。
@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. 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."}
@@ -0,0 +1,79 @@
# 实施计划
## 1. 公共路由合同
- [x] 盘点采购/资产页面中的列表 state、详情 state、表单 section、纯函数和数据 query,按 `design.md` 固定最小拆分边界。
- [x] 新增统一的采购/资产 canonical path builder,并为路径段编码、空 ID 和 `/assets/new` 静态优先级写聚焦测试。
- [x] 扩展 `pcDetailNavigation` 的显式来源 allowlist:保留报销/审批现状,增加采购列表、资产列表和受控项目详情来源;拒绝外部、hash、当前页和跨模块来源。
- [x] 增加采购/资产 legacy `?id=` 入口解析,在列表 query 前完成 replace,并把归一后的剩余查询写入安全 `state.from`。
- [x] 在 router、lazy exports 和 page meta 中注册采购详情、资产创建、资产详情;资产创建仅 `asset.manage`,其余动态路由复制原模块 guard。
## 2. 列表筛选 URL 化
- [x] 采购列表改为从 URL 读取 `status/scope/supplierId/legalEntityId/costCenterId/page`,筛选变化 replace、非页码筛选把 page 重置为 1、越权 scope 回落 own。
- [x] 资产列表改为从 URL 读取 `q/status/assetType/sensitivity/categoryId/legalEntityId/costCenterId/assigneeId/scope`,保留现有按权限决定的默认范围。
- [x] 公司变化与成本中心失配时原子清理 `costCenterId`;无效枚举、页码或范围不进入 API query。
- [x] 资产文本搜索使用 deferred/debounce 请求值,URL 仍反映用户当前输入;高级筛选面板由有效参数自动展开,手工开合可保持本地 UI state。
## 3. 采购迁移
- [x] 将 `PurchaseRequestsPage` 收敛为筛选、分页、列表、创建草稿入口和详情链接;列表路由不得请求详情。
- [x] 新建 `PurchaseRequestDetailPage`,搬迁 detail query、表单 hydration、附件、采购单归档、提交/撤回、状态动作和 dirty guard;详情路由不得请求完整列表。
- [x] 创建草稿成功后 push 到 `/purchase-requests/:purchaseRequestId` 并保留当前筛选来源。
- [x] 详情显式返回按安全来源显示“返回项目详情”或“返回采购列表”;取消 dirty 导航时保留 URL 与表单。
## 4. 资产迁移与独立创建
- [x] 将 `AssetRegistryPage` 收敛为筛选、资产列表、资产分类管理、移动扫码、创建入口和详情链接;列表路由不得请求单条详情。
- [x] 提取资产表单与动作 section 为创建/详情共用工作区,新建 `AssetDetailPage` 与 `AssetCreatePage`,不得复制两份长表单。
- [x] `/assets/new` 使用初始表单与 POST mutation;取消/离开沿用 dirty guard,失败保留输入。
- [ ] 创建成功后 replace 到 `/assets/:assetId` 并沿用原列表来源;验证浏览器后退不会回到已提交创建页。— PAUSED:实现与现有真实流 spec 断言已更新,当前本地数据库缺口阻塞实际提交回归。
- [x] 资产详情搬迁编辑、敏感字段、领用/归还/调拨和现有动作权限;详情 403/404 不泄露摘要。
## 5. 入口与移动兼容
- [x] 采购/资产列表行、项目采购关联、全局搜索 purchase/asset 解析、PC 通知解析统一使用 canonical path builder。
- [x] 全局搜索对采购/资产用 `entityType + entityId` 构造目标,不依赖服务端旧 `href`;不改搜索 API shape 或后端数据逻辑。
- [x] 搜索 `client/src` 中采购/资产 `?id=` 与 `highlight` 详情链接,只保留明确命名的 legacy 解析逻辑;兼容测试可保留旧 URL 输入。
- [x] 保持移动采购详情映射;补 `/assets/new`、`/assets/:assetId` 到 `/mobile/assets/scan` 的移动工作台回落,不新增移动资产详情。
- [x] 不新增不存在的审批/关联入口,不改合同、差旅、付款、请假功能。
## 6. 错误、权限与回归矩阵
- [x] 403/404 共用“记录可能不存在或无权查看”安全错误态,并提供模块列表返回入口。
- [x] 验证列表与详情的 route guard、服务端数据范围和页面内细粒度动作权限与迁移前一致;直接访问 `/assets/new` 必须验证 `asset.manage`。
- [x] 验证直接深链、刷新、列表显式返回、项目来源返回、浏览器前进/后退、筛选来源和 legacy replace。
- [x] 验证 dirty-form 在采购详情、资产详情和资产创建的取消/确认路径;取消不得改变 URL 或输入。
- [x] 验证 `/assets/new` 不请求 `/api/assets/new`,采购/资产列表不请求详情,详情不请求完整列表。
- [x] 更新人工 witness/视觉证据中的 canonical URL,但不得把本地 Chromium 通过扩张为企业微信真机、部署或公网通过。
## 7. 测试命令
- [x] `cd client && npm run typecheck`
- [x] `cd client && npm run build`
- [ ] `cd client && npx playwright test tests/e2e/procurement-flow.spec.ts tests/e2e/assets-flow.spec.ts tests/e2e/r103-cross-page-ui-consistency.spec.ts tests/e2e/pc-detail-mobile-visual-wave3.spec.ts tests/e2e/global-search.spec.ts --workers=1 --reporter=list` — PAUSED:真实 `procurement-flow`、`assets-flow` 与权限直连依赖当前本地数据库,既有缺列导致 `/api/me` 500;未为本前端任务迁移或重置数据库。
- [x] 运行新增的聚焦路由合同 spec,以及 notificationLinks、runtimeContext、项目关联和 page meta 的受影响用例。
- [x] `rg -n '/purchase-requests\\?id=|/assets\\?id=|/purchase-requests\\?highlight=|/assets\\?highlight=' client/src`,逐项确认只剩 legacy parser 或明确非详情用途。
- [x] `python3 ./.trellis/scripts/task.py validate 08-05-business-detail-routing`
- [x] `node scripts/qa/check-process-contract.mjs --baseline --json`
- [x] `node scripts/qa/audit-docs.mjs --check-registry`
- [x] `node scripts/qa/check-trellis-governance.mjs`
- [x] `git diff --check`
## 8. 检查、提交与回滚
- [x] 使用 `trellis-check` 做实现后质量检查;因新增/复制路由权限 guard,按 `AGENTS.md` 启动只读 reviewer,专门确认未扩大采购/资产访问范围,reviewer 不改代码。
- [x] 仅在形成可复用路由/返回契约时更新现有 `.trellis/spec/` 或事实源,不新建无必要文档。
- [ ] 使用中文 commit message 并引用 `08-05-business-detail-routing`;提交前确认无后端 API/schema、权限模型、差旅/付款/请假或真实数据变化。
- [x] 回滚必须同时恢复 route registry、lazy exports、page meta、canonical path builder、前端消费者、筛选 state 和旧 `?id=` 页面形态。
## 9. 验证记录(2026-08-07)
- PASS:`npm run typecheck`;`npm run build`(Vite 2404 modules,构建完成)。
- PASS:Chromium(mock API)聚焦回归 29/29,包括 canonical/legacy、筛选与返回、请求职责隔离、403/404、`/assets/new` 权限、dirty guard、全局搜索、移动回落和 1280px 资产详情。
- PASS:路由覆盖反例单测 29/29;`run-local-candidate.sh list` 内置检查 52/52,76 条应用路由 0 个未分类或阻断缺口。
- PASS:任务校验、process contract、Trellis governance、敏感内容扫描与 `git diff --check`;docs audit 退出 0,保留仓库既有 22 个未登记文档 warning,本任务未新增文档。
- PASS:只读 reviewer 未修改文件,确认采购/资产 route guard 与迁移前等价、`/assets/new` 更窄,URL scope 与后端对象范围均未扩大。
- PASS:R103 截图改写为自身 `output/playwright/r103`,不再覆盖已归档任务证据。
- PAUSED:真实 `procurement-flow`、`assets-flow` 与权限直连依赖的本地数据库缺少既有 `employees.bank_institution_id` 列,`/api/me` 返回 500;本任务不授权迁移、重置或真实数据写入。
- NOT RUN:部署、公网、生产配置与企业微信真机;不得据此宣称线上可测试。
@@ -0,0 +1,73 @@
# 采购与资产详情独立路由化
## Goal
把采购、资产两个已有业务台账从“列表页用 `?id=` 填充详情态”迁移为“列表查找、独立详情处理”,让记录可深链、筛选可恢复、返回可预测;资产新建使用独立创建路由,避免把完整长表单重新塞回台账列表。
## Confirmed Facts
- 报销、项目和审批任务已有独立详情路由与安全 `state.from` 返回约定,可作为仓库内成熟模式。
- 采购、资产都已有完整详情/编辑能力,并以列表页 `?id=`、本地 selected ID 和 searchParams 双向同步驱动;本任务是搬迁既有信息结构,不重做业务详情。
- 采购“新建采购”会先创建草稿并取得实体 ID,因此创建成功后可直接进入 `/purchase-requests/:purchaseRequestId`,不需要采购新建路由。
- 资产“新增资产”在提交前只有本地长表单、没有实体 ID;历史任务已确认“新建资产卡片进入独立创建视图”,本轮采用 `/assets/new`。
- 两个列表的筛选目前主要存在组件本地 state;拆出详情后若不把可恢复筛选写入 URL,返回列表会丢失用户上下文。
- 两页都已使用 `useUnsavedChangesGuard`,路由迁移必须保留取消离开与确认离开的既有语义。
- 项目详情、全局搜索和 PC 通知仍存在旧采购/资产入口:项目详情生成采购 `?id=`,全局搜索依赖服务端 `href`,PC 通知生成未被页面消费的 `highlight`。
- 现有采购/资产 E2E 和人工 witness 多处直接访问 `?id=`,需要兼容重定向并同步结构化路由证据。
- 合同没有 selected ID 或详情态,属于从零建设详情页;已拆到 `08-05-contract-detail-workspace`。
- 完整差旅已冻结;付款批次和请假继续保持既有页面边界,均不纳入本任务。
## Scope
- 仅迁移采购、资产两个已有 `?id=` 详情态到独立实体详情路由,并新增资产独立创建路由 `/assets/new`。
- 包含列表纯化、可恢复筛选 URL 化、详情/创建页拆分、旧链接兼容、现有前端关联入口更新、权限 guard、返回/深链和未保存保护。
- 资产列表保留现有资产分类管理等列表级工具;“纯列表”指不再承载某条资产或采购记录的详情/编辑工作区。
- 不新建合同详情信息结构,不恢复差旅,不改付款批次或请假页面。
## Requirements
- R1:`/purchase-requests` 与 `/assets` 只承载标题、创建入口、可恢复筛选、列表/卡片、分页或现有列表级工具,不渲染单条记录详情工作区。
- R2:采购详情固定为 `/purchase-requests/:purchaseRequestId`,资产详情固定为 `/assets/:assetId`;均支持直接打开、刷新、复制链接和正确页面元数据。
- R3:资产创建固定为 `/assets/new`,仅 `asset.manage` 可访问;创建成功后替换为新资产详情路由,浏览器返回不得重新进入已提交的创建表单。
- R4:采购状态、范围、供应商、公司、成本中心、页码,以及资产搜索、状态、类型、敏感级别、分类、公司、成本中心、归属人和范围等可恢复筛选以 URL 为事实源;无效值回落到安全默认,不扩大数据范围。
- R5:只搬迁现有详情、编辑、附件、审计、状态动作、资产流转和权限显隐,不新增合同式信息结构或改变业务能力。
- R6:从列表或资产创建页进入详情时保留经过校验的返回来源;从项目、全局搜索和通知等现有入口进入详情时使用新 URL。浏览器后退沿历史返回,显式返回在无安全来源时回到对应模块列表。
- R7:路由离开、显式返回、浏览器后退或跳转其他记录时存在未保存修改,必须沿用现有 dirty-form 保护;取消后保留当前 URL、记录与输入。
- R8:旧 `/purchase-requests?id=<id>`、`/assets?id=<id>` 使用 replace 进入新详情路由,并把移除 `id` 后的安全列表查询保留为返回来源;空 ID 不触发详情,无效/无权限 ID 不泄露记录摘要。
- R9:详情 403/404 使用统一自然中文安全错误态,仅说明“记录可能不存在或无权查看”并提供返回列表入口,不区分可用于资源枚举的敏感细节。
- R10:新列表、详情和创建路由沿用现有权限与服务端数据范围;资产 `/assets/new` 使用比资产列表更窄的 `asset.manage` guard,不得把列表可见权限误当创建权限。
- R11:采购创建成功、采购/资产列表行、项目关联、全局搜索结果解析、PC 通知解析和受影响测试改用统一的采购/资产详情路径构造;`client/src` 不再新增业务性的采购/资产 `?id=` 或 `highlight` 详情链接,兼容入口除外。
- R12:合同、差旅、付款、请假明确排除,不做顺手重构;移动采购详情保持现状,资产 PC 新路由在移动工作台偏好环境中仍安全回落到既有移动资产扫码页。
## Acceptance Criteria
- [x] `/purchase-requests` 与 `/assets` 首屏均为列表态,且不请求单条记录详情;资产分类管理等现有列表级工具仍可用。
- [x] `/purchase-requests/:purchaseRequestId` 与 `/assets/:assetId` 可直接访问、刷新和复制,页面标题、导航激活、权限 guard 与安全错误态正确。
- [ ] `/assets/new` 展示现有资产创建表单,非 `asset.manage` 用户不能直接访问;创建成功后 URL 变为 `/assets/:assetId`,返回不会回到已提交表单。— PAUSED:路由、权限与静态优先级已在 Chromium 通过;真实创建成功与历史栈因既有本地数据库列缺失未执行。
- [x] 采购和资产的全部可恢复列表筛选可由 URL 还原;列表进入详情再返回时筛选、范围和采购页码保持一致,筛选变更不会污染浏览器返回历史。
- [x] 旧 `?id=` 链接 replace 到对应新详情路由,返回时不丢失移除 `id` 后的安全筛选;空 ID 留在列表,无效或无权限 ID 不出现空白、错误对象或敏感摘要。
- [ ] 编辑、附件、状态动作、审计、资产流转和确认能力与迁移前等价,未保存保护覆盖详情返回、创建取消、浏览器后退和跨路由离开。— PAUSED:mock 路由与未保存保护已通过;真实采购附件/状态及资产流转等价回归受本地数据库缺口阻塞。
- [ ] 采购创建成功直接进入新详情;项目关联、全局搜索和 PC 通知中的采购/资产记录均进入新详情 URL,不再依赖未消费的 `highlight`。— PAUSED:项目、搜索与通知入口已验证;真实采购创建成功链路受本地数据库缺口阻塞。
- [x] `/assets/new` 不被动态资产详情路由当成 `assetId`,详情路由不加载完整列表,列表路由不加载详情。
- [x] 移动采购详情路由不回退;移动工作台偏好环境访问资产列表、详情或创建 URL 时继续进入既有 `/mobile/assets/scan`,不新增移动资产详情能力。
- [x] 合同、差旅、付款和请假代码不因本任务发生功能性修改;后端 endpoint、payload、权限/数据逻辑和数据库均不改变。
- [ ] 聚焦 Playwright 覆盖列表进入、筛选深链、详情刷新、返回恢复、旧链兼容、资产创建、无权限/不存在、dirty-form 取消与确认,以及项目/搜索/通知入口。— PAUSED:29 项 mock Chromium 回归已通过;真实资产创建和完整业务链仍受本地数据库缺口阻塞。
- [ ] `cd client && npm run typecheck && npm run build`、相关现有 E2E、任务校验与 Trellis 门禁通过。— PAUSED:类型、构建、mock E2E 与全部治理门禁已通过;`procurement-flow`、`assets-flow` 和真实权限直连用例未能在当前本地数据库执行。
## Out of Scope
- 合同详情页或合同文件能力。
- 差旅、付款批次、请假详情路由。
- 采购独立新建路由;采购继续先创建草稿再进入详情。
- 新移动资产详情页、移动资产创建页或移动导航重写。
- 后端 API endpoint/payload、状态机、权限模型、数据库 schema 或真实数据迁移。
- 模块冻结、一级导航调整、功能下线、详情信息架构重做或视觉换肤。
## Test Plan
- `cd client && npm run typecheck && npm run build`
- 聚焦 Playwright 覆盖采购/资产筛选 URL、列表进入详情、深链刷新、显式返回与浏览器后退、旧 `?id=` replace、无权限/不存在和 dirty-form 取消/确认。
- 覆盖 `/assets/new` 的权限、未保存取消、创建成功 replace、`new` 静态路由优先级,以及采购创建成功后的详情导航。
- 更新并运行 `procurement-flow.spec.ts`、`assets-flow.spec.ts`、`r103-cross-page-ui-consistency.spec.ts`、`pc-detail-mobile-visual-wave3.spec.ts`、`global-search.spec.ts`、通知链接与移动路由受影响用例;必要时增加聚焦路由合同 spec。
- 用代码搜索确认 `client/src` 中业务入口不再产生采购/资产 `?id=` 或 `highlight` 详情链接,只保留明确命名的兼容解析逻辑。
- 运行 `python3 ./.trellis/scripts/task.py validate 08-05-business-detail-routing`、`node scripts/qa/check-process-contract.mjs --baseline --json`、`node scripts/qa/audit-docs.mjs --check-registry`、`node scripts/qa/check-trellis-governance.mjs` 和 `git diff --check`。
@@ -0,0 +1,48 @@
{
"id": "business-detail-routing",
"name": "business-detail-routing",
"title": "业务详情独立路由化",
"description": "将采购与资产已有列表内详情态迁移为独立实体路由,为资产提供独立创建路由,并兼容旧链接、筛选返回、权限与未保存保护。",
"status": "completed",
"dev_type": null,
"scope": "client-procurement-and-asset-routes",
"package": null,
"priority": "P2",
"creator": "shunli",
"assignee": "shunli",
"createdAt": "2026-08-05",
"completedAt": "2026-08-07",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": "08-05-product-interaction-consistency",
"relatedFiles": [
"client/src/pages/PurchaseRequestsPage.tsx",
"client/src/pages/AssetRegistryPage.tsx",
"client/src/pages/ProjectDetailPage.tsx",
"client/src/components/GlobalSearchDialog.tsx",
"client/src/router/index.tsx",
"client/src/router/lazy/index.ts",
"client/src/features/navigation/schema.ts",
"client/src/lib/pcDetailNavigation.ts",
"client/src/lib/businessDetailRoutes.ts",
"client/src/lib/notificationLinks.ts",
"client/src/lib/runtimeContext.ts",
"client/src/router/lazy/PurchaseRequestDetailPage.ts",
"client/src/router/lazy/AssetCreatePage.ts",
"client/src/router/lazy/AssetDetailPage.ts",
"scripts/qa/route-manifest.json",
"client/tests/e2e/business-detail-routing.spec.ts",
"client/tests/e2e/procurement-flow.spec.ts",
"client/tests/e2e/assets-flow.spec.ts",
"client/tests/e2e/r103-cross-page-ui-consistency.spec.ts"
],
"notes": "本地实现、类型构建、29 项 Chromium 聚焦回归、路由覆盖与治理门禁已完成;只读 reviewer 确认未扩大采购/资产权限范围。真实 procurement-flow、assets-flow 与权限直连因既有本地数据库缺列导致 /api/me 500 而 PAUSED,未迁移或重置数据库;部署、公网和企业微信真机未执行。",
"meta": {
"description": "将采购与资产已有列表内详情态迁移为独立实体路由,为资产提供独立创建路由,并兼容旧链接、筛选返回、权限与未保存保护。"
}
}