docs(08-05-product-interaction-consistency): 固化交互治理任务规划与规范
这个提交包含在:
@@ -252,13 +252,13 @@ reference, not a copy of Feishu or Ant Design branding.
|
||||
detail panels, confirmations and more menus. Project-specific differences
|
||||
should simplify or hide low-frequency work for small teams, not invent new
|
||||
controls or unusual action flows.
|
||||
- Page titles default to the route name only, such as `工作台`, `待我处理`, `报销`,
|
||||
- Page titles default to the route name only, such as `工作台`, `待我审批`, `报销`,
|
||||
`合同`, `采购`, `资产`, `个人设置` and `通知中心`. Page subtitles are off by default and
|
||||
appear only for exception, empty, permission-denied, Enterprise WeChat/OA
|
||||
boundary, external-system boundary or irreversible-action explanations.
|
||||
- Normal workflows should not be explained in default-visible copy. Do not add
|
||||
subtitles, card descriptions, helper rows or notice bars that restate obvious
|
||||
actions such as "待我处理是处理工作", "查看通知消息", "先核对再处理" or "去处理要做的事".
|
||||
actions such as "待我审批是处理工作", "查看通知消息", "先核对再处理" or "去处理要做的事".
|
||||
- Header primary actions are reserved for creation or a real page-level primary
|
||||
action: `新建报销`, `新建采购`, `新增合同`, `新增资产`, `新建项目`. Workbench,
|
||||
pending-task, notification and read-only record pages must not force a header
|
||||
@@ -274,7 +274,7 @@ reference, not a copy of Feishu or Ant Design branding.
|
||||
`撤回`, `作废`, `归档`, `更多`. Do not use slogan-like or invented labels
|
||||
such as `处理最急的一件`.
|
||||
- Sidebar entries are role-aware common work entries, not an all-permissions
|
||||
module catalog. Direct entries may include `工作台`, `待我处理`, `报销`, `采购`,
|
||||
module catalog. Direct entries may include `工作台`, `待我审批`, `报销`, `采购`,
|
||||
`合同`, `项目`, `资产`, `付款`, `应付账款`, `票据`, `团队`, `请假记录` and `管理`
|
||||
when the current role has real use. Low-frequency, frozen, staged or
|
||||
troubleshooting routes stay in `更多` or the `管理` landing page.
|
||||
@@ -329,13 +329,13 @@ reference, not a copy of Feishu or Ant Design branding.
|
||||
| Surface | Template | First-screen content | Primary action rule |
|
||||
| --- | --- | --- | --- |
|
||||
| `工作台` | 工作台总览 | Compact counts, common entries, real reminders | No forced header action. Use `去处理`, `新建报销`, `查看全部` inside relevant sections; do not show helper sentences for ordinary items. |
|
||||
| `待我处理` | 任务处理列表 | `待处理` / `已处理` tabs, count, minimal filters, task list | Row action is `处理`; `已处理记录` is a tab, not a separate header button. |
|
||||
| `待我审批` | 任务处理列表 | `待处理` / `已处理` tabs, count, minimal filters, task list | Row action is `处理`; `已处理记录` is a tab, not a separate header button. |
|
||||
| `审批详情` | 详情工作区 | Summary, facts/evidence, right summary/action panel, history | One action bar: primary `通过`, secondary `退回`/`驳回`, `转审` in `更多`. |
|
||||
| `报销` / `采购` / `合同` / `项目` / `资产` / `付款` / `应付账款` / `票据` | 业务台账列表 | One-row common filters plus table/card list | Header creation only when the route owns creation; risky tools use `更多`, clearly named role-only actions or detail pages; no default left-list/right-detail split. |
|
||||
| Business details | 详情工作区 | Summary, facts, evidence, relations, history | Right-side state/action panel on PC; bottom primary action on mobile. |
|
||||
| `管理` | 管理配置页 | Grouped landing: `人员组织`, `审批流程`, `基础资料`, `系统设置`, `审计排障` | Management subpages use search/filter, table and right operation column. |
|
||||
| `个人设置` | 管理配置页 variant | `个人资料`, `账号安全`, `通知偏好`, `企业微信绑定` | No employee dossier, permission matrix or org maintenance in the default profile path. |
|
||||
| `通知中心` | 业务台账列表 variant | Event inbox for state changes, reminders, failures and sync results | Notification click deep-links to business detail, workflow task or failure explanation; never duplicates `待我处理`. |
|
||||
| `通知中心` | 业务台账列表 variant | Event inbox for state changes, reminders, failures and sync results | Notification click deep-links to business detail, workflow task or failure explanation; never duplicates `待我审批`. |
|
||||
| `请假记录` / `考勤` | 业务台账列表 or record view | Enterprise WeChat-owned records, mirror status, exceptions and audit | Ordinary users do not see OA daily leave/punch approval creation; OA emergency entry only for authorized emergency/admin scope. |
|
||||
| Mobile routes | Mobile expression of the same template | Top status/focus, card list or sectioned detail | One bottom safe-area primary action; secondary actions in `更多` or confirmation. |
|
||||
|
||||
@@ -682,7 +682,7 @@ point and prevents each route from inventing a different title/action layout.
|
||||
the primary sidebar, including `合同`, `采购`, `资产` and `项目` when relevant.
|
||||
`更多` is for low-frequency, frozen, staged or occasional entries, not a
|
||||
place to bury common work.
|
||||
- Approvers see `待我处理`; ordinary employees should not see `待办` or
|
||||
- Approvers see `待我审批`; ordinary employees should not see `待办` or
|
||||
`审批待办` in the sidebar by default.
|
||||
- PC leave navigation uses `请假记录` unless the current task explicitly opens OA
|
||||
emergency leave submission.
|
||||
@@ -844,11 +844,11 @@ of exposing every allowed tool at once.
|
||||
default.
|
||||
- Initiator views emphasize current status, next step, supplement-material
|
||||
needs, approval/payment progress and whether edit or withdraw is allowed.
|
||||
- Approver and processor views emphasize `待我处理` and the current node task.
|
||||
- Approver and processor views emphasize `待我审批` and the current node task.
|
||||
Show approve, reject, return or transfer actions only when the server-backed
|
||||
task assignment says the current user can act.
|
||||
- Approval actions such as approve, reject, return and transfer execute only in
|
||||
`待我处理`, workflow task detail, mobile approval detail or an explicitly
|
||||
`待我审批`, workflow task detail, mobile approval detail or an explicitly
|
||||
current task-processing panel. Business ledger rows and ordinary business
|
||||
details use `查看处理`, `查看进度`, `补材料` or a link into the workflow task;
|
||||
they must not duplicate approval action buttons.
|
||||
@@ -946,7 +946,7 @@ screen.
|
||||
- Keep 3 to 5 high-frequency filters visible by default. Prioritize search,
|
||||
status, scope and date/time; add business-specific filters such as project,
|
||||
person or type only when they are genuinely frequent.
|
||||
- The default view must match the route's main job: `我的报销`, `待我处理`,
|
||||
- The default view must match the route's main job: `我的报销`, `待我审批`,
|
||||
`合同`, `资产` and similar task nouns. Do not default ordinary users
|
||||
into company-wide management views.
|
||||
- Rows and cards must answer what the object is, current state, why it relates
|
||||
@@ -1349,6 +1349,89 @@ complexity from leaking into WeChat.
|
||||
- If true Enterprise WeChat device validation is unavailable, record it as
|
||||
paused manual validation; do not claim full WebView acceptance.
|
||||
|
||||
## Scenario: Shared Mobile Sticky Action Surface
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: a mobile detail, approval or edit surface needs a bottom fixed
|
||||
primary-action area, or an existing bottom action area is changed.
|
||||
- Applies to mobile consumers and responsive business edit forms; it does not
|
||||
move business permission or state-machine decisions into the shared shell.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Component: `MobileStickyActionBar({ children, className?, contentClassName?, inlineFromMedium?, testId? })` from
|
||||
`client/src/components/patterns/MobileStickyActionBar.tsx`.
|
||||
- Page spacing constant: `MOBILE_STICKY_ACTION_PAGE_CLASS`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- New or changed mobile bottom actions reuse `MobileStickyActionBar`; do not
|
||||
copy its fixed positioning, safe-area, grid or shadow classes into a page.
|
||||
- When a fixed action bar is rendered, the page's scroll/content container also
|
||||
receives `MOBILE_STICKY_ACTION_PAGE_CLASS` so the final field, evidence card
|
||||
or history row remains above the bar and device safe area.
|
||||
- Apply the page class conditionally when actions are conditional; an absent bar
|
||||
must not leave unexplained fixed-action padding.
|
||||
- The shared component owns coordinates, safe-area treatment, responsive inline
|
||||
conversion and action layout only. Consumers still own button text,
|
||||
permission/state visibility, confirmation, pending and disabled behavior.
|
||||
- `inlineFromMedium` may convert the action surface to an inline desktop/tablet
|
||||
row for shared edit templates; mobile detail routes normally keep the fixed
|
||||
form.
|
||||
- `PaymentBatchesPage` retains its existing responsive payment-specific bar as
|
||||
a known legacy exception until a payment-scoped task proves migration has no
|
||||
business or layout regression. It is not a pattern to copy.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Bar visible + page class present -> final content remains reachable above the
|
||||
fixed surface.
|
||||
- Bar visible + page class missing -> spacing-contract failure.
|
||||
- Bar hidden + unconditional page class -> unnecessary-padding failure.
|
||||
- Consumer moves approval/payment state logic into the shared component ->
|
||||
ownership-contract failure.
|
||||
- Narrow viewport or device safe area -> actions remain readable and are not
|
||||
covered by the home indicator.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: mobile approval and mobile reimbursement share the same action surface
|
||||
while each page keeps its own action matrix and confirmation behavior.
|
||||
- Base: `BusinessEditTemplate` uses `inlineFromMedium` and the same page-spacing
|
||||
constant when bottom actions exist.
|
||||
- Bad: paste a page-local `fixed inset-x-0 bottom-0` wrapper and compensate with
|
||||
an unrelated hard-coded `pb-24`.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- A source contract asserts mobile approval, mobile reimbursement and
|
||||
`BusinessEditTemplate` import the shared component and page-spacing constant.
|
||||
- Focused Playwright covers at least two mobile consumers, including final
|
||||
content reachability, visible/pending/disabled action states and a narrow
|
||||
viewport safe-area regression.
|
||||
- Run `cd client && npm run typecheck && npm run build`.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```tsx
|
||||
<main className="pb-24">
|
||||
...
|
||||
<div className="fixed inset-x-0 bottom-0">{actions}</div>
|
||||
</main>
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```tsx
|
||||
<main className={cn(showActions && MOBILE_STICKY_ACTION_PAGE_CLASS)}>
|
||||
...
|
||||
{showActions ? <MobileStickyActionBar>{actions}</MobileStickyActionBar> : null}
|
||||
</main>
|
||||
```
|
||||
|
||||
## Convention: Notifications Versus Tasks
|
||||
|
||||
**What**: Task queues and notifications have separate jobs. A task queue is for
|
||||
@@ -1356,7 +1439,7 @@ action; a notification center is for event awareness and navigation.
|
||||
|
||||
**Contracts**:
|
||||
|
||||
- `待我处理` is an action list. Include only items that require the current user
|
||||
- `待我审批` is an action list. Include only items that require the current user
|
||||
to process, supplement material, review, pay, approve, confirm or resolve an
|
||||
exception.
|
||||
- Task rows must have a clear next step, object summary, business state and
|
||||
@@ -1482,6 +1565,88 @@ structure.
|
||||
permission-denied pages, notification failure targets or shared error
|
||||
components.
|
||||
|
||||
## Scenario: Ordinary User Error Message Resolution
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: a frontend component catches an API or JavaScript error and needs to
|
||||
show failure feedback on an ordinary employee, approver, finance or mobile
|
||||
path.
|
||||
- Explicit exception: `client/src/pages/AuditLogsPage.tsx` is a permissioned,
|
||||
read-only technical detail surface and may display the minimum API code beside
|
||||
its error message.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Shared resolver:
|
||||
`resolveErrorMessage(error: unknown, fallback = "操作失败,请稍后重试"): string`
|
||||
from `client/src/lib/resolveErrorMessage.ts`.
|
||||
- API business mapping remains
|
||||
`mapApiErrorMessage(code, details, fallbackMessage)` in
|
||||
`client/src/api/errorMessages.ts`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Ordinary user feedback calls `resolveErrorMessage` instead of concatenating
|
||||
`error.code`, `error.message`, request methods or API paths locally.
|
||||
- API codes may still drive control flow, such as detecting an already-processed
|
||||
task or a disabled capability, but they must not be reused as display copy.
|
||||
- The resolver returns mapped natural Chinese, removes a leading technical code
|
||||
prefix and falls back when the candidate is empty, lacks natural Chinese or
|
||||
resembles a URL, API/filesystem path, HTTP request line, stack frame or raw
|
||||
JSON payload.
|
||||
- Feedback placement remains separate from message parsing: field/material
|
||||
problems are local, region load failures are persistent and retryable, and
|
||||
short success feedback may use toast.
|
||||
- `AuditLogsPage` is the only current code-plus-message exception. New admin or
|
||||
troubleshooting exceptions require an explicit technical detail area and a
|
||||
scoped Trellis decision; do not weaken the shared resolver.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Known API code with a mapped business message -> show the mapped natural
|
||||
Chinese only.
|
||||
- Candidate starts with `VALID_001:` or `500:` -> remove the prefix before
|
||||
display.
|
||||
- Candidate is English-only, contains `/api/`, a full path, HTTP method, stack
|
||||
frame or raw JSON -> show the caller-provided fallback.
|
||||
- Unknown value or empty message -> show the caller-provided fallback.
|
||||
- Audit-log load/export failure caused by `ApiError` -> the read-only audit
|
||||
error area may show `<code>: <message>`; ordinary pages may not.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: a reimbursement page uses
|
||||
`resolveErrorMessage(error, "加载报销失败,请稍后重试")` and keeps the retry
|
||||
action beside the persistent error.
|
||||
- Base: a page uses `isApiError(error)` only to branch on a business condition,
|
||||
then resolves any displayed failure through the shared helper.
|
||||
- Bad: each page removes a code prefix with its own regex or displays
|
||||
`` `${error.code}: ${error.message}` `` on an ordinary path.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- A source contract scans ordinary frontend consumers and rejects local
|
||||
code-plus-message concatenation while listing `AuditLogsPage` as the explicit
|
||||
exception.
|
||||
- Focused tests cover a mapped API error, a leading code prefix, a technical
|
||||
detail fallback and an unknown error.
|
||||
- Run `cd client && npm run typecheck && npm run build`.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```tsx
|
||||
setError(isApiError(error) ? `${error.code}: ${error.message}` : "加载失败");
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```tsx
|
||||
setError(resolveErrorMessage(error, "加载失败,请稍后重试"));
|
||||
```
|
||||
|
||||
## Convention: Dangerous Action Confirmation
|
||||
|
||||
**What**: Destructive, financial, approval, configuration, batch and external
|
||||
@@ -1726,7 +1891,7 @@ training or permission-maintenance surfaces:
|
||||
default. If first-login guidance is needed, use a dismissible welcome dialog
|
||||
with at most three real business entries.
|
||||
- Welcome or onboarding entries must point to executable user tasks, such as
|
||||
`处理待办`, `提交报销` or `请假`; do not use explanatory entries like
|
||||
`待我审批`, `提交报销` or `请假`; do not use explanatory entries like
|
||||
`了解流程`, and do not route users to settings or notifications as a
|
||||
substitute for task guidance.
|
||||
- `个人设置` default content is personal profile, password/security,
|
||||
|
||||
@@ -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,56 @@
|
||||
# 设计说明
|
||||
|
||||
## 1. 架构边界
|
||||
|
||||
本任务只迁移采购、资产已有详情态。列表数据、详情 API、mutation、权限和表单字段保持原合同,不为路由化新增后端接口或数据库字段。
|
||||
|
||||
目标路由:
|
||||
|
||||
| 模块 | 列表 | 详情 |
|
||||
| --- | --- | --- |
|
||||
| 采购 | `/purchase-requests` | `/purchase-requests/:purchaseRequestId` |
|
||||
| 资产 | `/assets` | `/assets/:assetId` |
|
||||
|
||||
## 2. 页面拆分
|
||||
|
||||
- `PurchaseRequestsPage` 与 `AssetRegistryPage` 收敛为纯列表页,只持有筛选、分页、列表 query、创建入口和行级“查看详情”。
|
||||
- 新建 `PurchaseRequestDetailPage` 与 `AssetDetailPage`,承接现有详情 query、表单 hydration、附件/动作、右侧状态动作和 dirty 状态。
|
||||
- 先提取页面内纯函数、类型和可复用 section,避免复制大段表单逻辑;不为了抽象而建立通用“万能详情页”。
|
||||
- 创建行为保持现有 API 合同:创建成功后导航到新详情路由;本任务不新增独立新建 URL。
|
||||
|
||||
## 3. 导航与返回
|
||||
|
||||
- 列表进入详情时通过 React Router history push,并在 location state 保存安全的当前列表 URL;浏览器后退自然恢复原筛选和滚动历史。
|
||||
- 详情显式“返回”优先使用经过校验的同模块 `state.from`,无来源或刷新后回退到模块列表。
|
||||
- 项目关联、全局搜索、审批/通知等入口直接生成新详情 URL;不允许把任意外部字符串作为 return URL。
|
||||
- `features/navigation/schema.ts` 添加采购详情、资产详情元数据,并确保长路径优先匹配。
|
||||
|
||||
## 4. 旧链接兼容
|
||||
|
||||
- `/purchase-requests?id=<uuid>` 与 `/assets?id=<uuid>` 在列表路由入口执行 replace 重定向到新详情路径。
|
||||
- 重定向移除 `id`,保留其余无关查询参数;无效或空 ID 不重定向,列表正常展示。
|
||||
- 仓库内消费者全部改为新 URL,旧格式只作为外部收藏、历史通知和人工 witness 的兼容层。
|
||||
|
||||
## 5. Dirty guard 与错误态
|
||||
|
||||
- 详情页面继续注册 `useUnsavedChangesGuard`;返回按钮、浏览器后退、跨模块链接和直接路由离开都由 provider 阻断。
|
||||
- 用户取消离开时保留当前 ID 和所有输入;确认后才执行导航。
|
||||
- 服务端 403/404 使用普通中文错误态。为避免资源枚举,详情页不区分“无权限”与“记录不存在”的敏感摘要,只提供安全返回列表入口。
|
||||
- 列表路由不发详情 query;详情路由不为构造页面偷偷加载完整列表。
|
||||
|
||||
## 6. 权限与兼容
|
||||
|
||||
- 新动态路由复制对应列表路由的 `RequirePermission` guard;页面内现有细粒度动作权限继续保留。
|
||||
- API endpoint、请求 payload、审计、高风险确认、附件下载原因和状态矩阵均不改变。
|
||||
- 移动采购现有 `/mobile/purchase-requests/:id` 不受影响;本任务不新增移动资产详情路由。
|
||||
|
||||
## 7. 测试策略
|
||||
|
||||
- 为两个模块建立同构的路由合同测试:列表纯净、进入详情、直接深链、刷新、返回恢复、旧链 replace、无效/无权限、dirty 取消/确认。
|
||||
- 更新现有 `procurement-flow.spec.ts`、`assets-flow.spec.ts`、`r103-cross-page-ui-consistency.spec.ts`、`pc-detail-mobile-visual-wave3.spec.ts` 和人工 witness 中的旧链接。
|
||||
- 测试应使用显式 route tag/URL 断言,不再用父列表路径字符串误认动态详情证据。
|
||||
|
||||
## 8. 回滚
|
||||
|
||||
- 新路由、页面拆分、链接消费者和兼容 redirect 同一任务提交;回滚时整体恢复 `?id=`,避免一半消费者指向不存在的路由。
|
||||
- 无 schema、API 或数据迁移。应用回滚不需要处理数据库。
|
||||
@@ -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,43 @@
|
||||
# 实施计划
|
||||
|
||||
## 1. 公共准备
|
||||
|
||||
- [ ] 盘点采购/资产页面中列表状态、详情状态、纯函数和共享 section,确定最小拆分边界。
|
||||
- [ ] 增加安全的列表来源/返回 helper 与旧 `?id=` redirect helper,先写单元或 Playwright 合同。
|
||||
- [ ] 在 router 和 page meta 中注册两个详情路径,复制原权限 guard。
|
||||
|
||||
## 2. 采购迁移
|
||||
|
||||
- [ ] 将采购列表收敛为筛选、列表、创建入口和查看详情。
|
||||
- [ ] 新建采购详情页,搬迁 detail query、表单、附件、归档、提交/撤回和 dirty guard。
|
||||
- [ ] 创建成功、项目关联、通知/审批/搜索等入口改为 `/purchase-requests/:id`。
|
||||
- [ ] 旧 `/purchase-requests?id=` replace 到新路由并保留其他查询条件。
|
||||
|
||||
## 3. 资产迁移
|
||||
|
||||
- [ ] 将资产列表收敛为筛选、列表、创建入口和查看详情。
|
||||
- [ ] 新建资产详情页,搬迁 detail query、编辑、调拨/盘点相关入口、敏感字段和 dirty guard。
|
||||
- [ ] 创建成功、项目关联、搜索和其他入口改为 `/assets/:id`。
|
||||
- [ ] 旧 `/assets?id=` replace 到新路由并保留其他查询条件。
|
||||
|
||||
## 4. 回归与兼容
|
||||
|
||||
- [ ] 搜索并消除仓库内新产生的采购/资产 `?id=` 详情链接;只保留兼容测试。
|
||||
- [ ] 验证直接深链、刷新、返回列表、浏览器前进/后退和筛选来源。
|
||||
- [ ] 验证无权限/不存在不泄露摘要,列表页不请求详情。
|
||||
- [ ] 验证 dirty-form 取消保留输入、确认后离开。
|
||||
- [ ] 验证移动采购路由、合同、差旅、付款和请假未被改动。
|
||||
|
||||
## 5. 测试命令
|
||||
|
||||
- [ ] `cd client && npm run typecheck`
|
||||
- [ ] `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 --workers=1 --reporter=list`
|
||||
- [ ] 按修改范围补跑 project/notification/search 关联入口用例。
|
||||
- [ ] 运行 task validate、process contract、docs registry、Trellis governance 和 `git diff --check`。
|
||||
|
||||
## 6. 提交与回滚
|
||||
|
||||
- [ ] 使用中文 commit message 并引用 `08-05-business-detail-routing`。
|
||||
- [ ] 提交前确认无后端 API、schema、权限、差旅/付款/请假或真实数据变化。
|
||||
- [ ] 回滚必须同时恢复动态路由、页面消费者、page meta 和旧 `?id=` 页面形态。
|
||||
@@ -0,0 +1,61 @@
|
||||
# 采购与资产详情独立路由化
|
||||
|
||||
## Goal
|
||||
|
||||
把采购、资产两个结构同构的业务台账从“列表页用 `?id=` 填充已有详情态”迁移为“纯列表查找、独立详情处理”,让详情可深链、浏览器返回可预测,并在切换对象时保留未保存保护。
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- 报销、项目和审批任务已有独立详情路由,可作为仓库内成熟模式。
|
||||
- 采购、资产都已经具有完整详情/编辑状态,并以列表页 `?id=`、本地 selected ID 和 searchParams 双向同步驱动。
|
||||
- 采购、资产未发现首屏自动选中第一条记录;本任务是搬迁既有详情态,不新增业务信息结构。
|
||||
- 两页都已使用 `useUnsavedChangesGuard`,路由迁移必须保留 dirty-form 取消与确认语义。
|
||||
- 现有采购/资产 E2E 和人工 witness 多处直接访问 `?id=`,需要兼容重定向并同步结构化路由证据。
|
||||
- 合同没有 selected ID 或详情态,属于从零建设详情页;已拆到 `08-05-contract-detail-workspace`。
|
||||
- 完整差旅已冻结,`/travel-requests` 与差旅报销指南无关;其自动选中缺陷归入低风险子任务,不做路由化。
|
||||
- 付款批次和请假继续保持既有页面边界,不纳入本任务。
|
||||
|
||||
## Scope
|
||||
|
||||
- 仅迁移采购、资产两个已有 `?id=` 详情态台账到独立实体详情路由。
|
||||
- 包含列表纯化、详情页拆分、旧链接兼容、关联入口更新、权限 guard、返回/深链和 dirty-form 保护。
|
||||
- 不新建合同详情信息结构,不恢复差旅,不改付款批次或请假页面。
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1:`/purchase-requests` 与 `/assets` 只负责标题、创建入口、常用筛选、列表/卡片和进入详情,不渲染记录详情工作区。
|
||||
- R2:采购与资产分别使用稳定的实体 ID 详情路由,支持直接打开、刷新、复制链接和浏览器返回;页面元数据识别详情层级。
|
||||
- R3:只搬迁现有详情、编辑、附件、审计、状态动作和权限显隐,不新增合同式信息结构或改变业务能力。
|
||||
- R4:从列表进入详情、从项目/通知/搜索等关联入口进入详情和返回列表时保留必要来源;浏览器后退不得直接跳出业务模块。
|
||||
- R5:路由离开、返回或切换对象存在未保存修改时,必须沿用现有 dirty-form 保护;取消后保留原记录和输入。
|
||||
- R6:旧 `/purchase-requests?id=<id>`、`/assets?id=<id>` 深链在过渡期重定向到新详情路由,并保留无关筛选参数;无效/无权限 ID 使用自然中文错误与安全返回入口。
|
||||
- R7:权限 guard 与服务端数据范围保持不变;不得因拆分路由扩大可访问范围。
|
||||
- R8:更新列表行、项目关联、全局搜索、审批/通知跳转和现有测试中的采购/资产详情链接,不继续产生新的 `?id=` 链接。
|
||||
- R9:合同、差旅、付款、请假明确排除,不做顺手重构。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 采购和资产台账首屏均为纯列表,未选择记录时不渲染详情工作区。
|
||||
- [ ] 两个模块都有可刷新、可复制、可直接访问的详情 URL,并具有正确标题、权限 guard 和安全错误态。
|
||||
- [ ] 列表筛选来源可在详情返回后合理恢复,浏览器前进/后退行为可预测。
|
||||
- [ ] 旧 `?id=` 链接兼容重定向到对应新路由,不出现静默空白、错误对象或丢失无关筛选条件。
|
||||
- [ ] 编辑、附件、状态动作、审计和确认能力与迁移前等价,未保存保护覆盖返回和路由离开。
|
||||
- [ ] 项目关联、搜索、通知/审批等仓库内入口不再生成新的采购/资产 `?id=` 详情链接。
|
||||
- [ ] 合同、差旅、付款和请假代码不因本任务发生功能性修改。
|
||||
- [ ] 聚焦 Playwright 覆盖两个模块的列表进入、深链刷新、返回恢复、旧链兼容、无权限/不存在、dirty-form 取消与确认。
|
||||
- [ ] `cd client && npm run typecheck && npm run build`、相关现有 E2E 与 Trellis 门禁通过。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 合同详情页或合同文件能力。
|
||||
- 差旅、付款批次、请假详情路由。
|
||||
- 后端状态机、权限模型、数据库 schema 或真实数据迁移。
|
||||
- 全站所有配置页和移动端路由重写。
|
||||
- 模块冻结、一级导航调整、功能下线或视觉换肤。
|
||||
|
||||
## Test Plan
|
||||
|
||||
- `cd client && npm run typecheck && npm run build`
|
||||
- 聚焦 Playwright 覆盖采购/资产列表进入、详情深链刷新、旧 `?id=` 重定向、筛选返回、无权限/不存在和 dirty-form 取消/确认。
|
||||
- 更新并运行 `procurement-flow.spec.ts`、`assets-flow.spec.ts`、`r103-cross-page-ui-consistency.spec.ts` 及受影响的关联入口用例。
|
||||
- 运行 `python3 ./.trellis/scripts/task.py validate 08-05-business-detail-routing`、process contract、docs registry、Trellis governance 和 `git diff --check`。
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"id": "business-detail-routing",
|
||||
"name": "business-detail-routing",
|
||||
"title": "业务详情独立路由化",
|
||||
"description": "将采购与资产已有列表内详情态迁移为独立实体路由,兼容旧链接并保留权限、返回和未保存保护。",
|
||||
"status": "planning",
|
||||
"dev_type": null,
|
||||
"scope": "client-procurement-and-asset-routes",
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shunli",
|
||||
"assignee": "shunli",
|
||||
"createdAt": "2026-08-05",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-05-product-interaction-consistency",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {
|
||||
"description": "将采购与资产已有列表内详情态迁移为独立实体路由,兼容旧链接并保留权限、返回和未保存保护。"
|
||||
}
|
||||
}
|
||||
@@ -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,133 @@
|
||||
# 设计说明
|
||||
|
||||
## 1. 总体结构
|
||||
|
||||
合同任务分为四个同批兼容层:
|
||||
|
||||
1. Prisma 增量字段与关系。
|
||||
2. 合同详情/终止/历史 API 与事务审计。
|
||||
3. 合同列表、详情、新建/编辑和响应式动作 UI。
|
||||
4. 状态机、权限、迁移和端到端验证。
|
||||
|
||||
本任务不建设合同附件库。证据区只消费现有台账字段和 `ConfidentialDocumentRef` 的最小摘要。
|
||||
|
||||
## 2. 数据模型
|
||||
|
||||
在 `ContractRegistry` 增加:
|
||||
|
||||
```prisma
|
||||
terminated_at DateTime?
|
||||
terminated_reason String? @db.VarChar(1000)
|
||||
terminated_by String? @db.Uuid
|
||||
terminator Employee? @relation("ContractRegistryTerminator", fields: [terminated_by], references: [id], onDelete: Restrict)
|
||||
```
|
||||
|
||||
在 `Employee` 增加反向关系 `terminated_contract_registries`。
|
||||
|
||||
迁移只增加三个可空列、外键和必要索引,不回填历史行,不修改 `status` 列类型。`terminated_reason` 请求 trim 后长度 10–1000。
|
||||
|
||||
数据库暂不增加跨列 CHECK;服务层与测试强制:
|
||||
|
||||
```text
|
||||
status = terminated <=> terminated_at/reason/by 全部非空
|
||||
status != terminated <=> terminated_at/reason/by 全部为空
|
||||
```
|
||||
|
||||
`terminated` 是终态,本轮不允许恢复、续约或再归档,避免单一状态列丢失终止事实。
|
||||
|
||||
## 3. 状态值域与写入边界
|
||||
|
||||
- `CONTRACT_REGISTRY_PERSISTED_STATUS_VALUES` 增加 `terminated`,筛选和派生状态自动识别。
|
||||
- `resolveContractRegistryEffectiveStatus` 继续只对持久状态 `active` 派生 `expired`;terminated 天然不被覆盖。
|
||||
- 到期提醒继续硬过滤 `active`,无需特殊排除逻辑,但增加回归断言。
|
||||
- 不把通用 create/PATCH schema 直接开放为可写 `terminated`。新增一般写状态集合 `draft/active/archived`,终止只能走专用接口,防止绕过三字段不变量。
|
||||
- 通用 PATCH 对 `terminated` 和 `archived` 主记录执行状态锁:terminated 不可编辑;archived 普通字段不可覆盖。
|
||||
|
||||
## 4. API 合同
|
||||
|
||||
### 4.1 详情
|
||||
|
||||
`GET /api/contracts-registry/:id`
|
||||
|
||||
- 使用与列表相同的数据范围条件查询,未授权与不存在统一安全 404。
|
||||
- 返回合同摘要、owner、project、terminator、三个终止字段、最小涉密资料索引和白名单操作历史。
|
||||
- `ConfidentialDocumentRef` 不返回完整 `storage_path`、access rule raw JSON 或其他敏感原文;只返回 ID、标题、资料编号、敏感级别、存储类型、离线档案号、责任人和时间等必要摘要。
|
||||
- 历史从 `AuditLog` 按 `target_type=contract_registry + target_id` 查询,只映射允许展示的 action、actor、time 和安全摘要,不直出 raw `extra`。
|
||||
|
||||
### 4.2 终止
|
||||
|
||||
`POST /api/contracts-registry/:id/terminate`
|
||||
|
||||
请求:`{ reason: string }`,trim 后 10–1000 字。
|
||||
|
||||
- 仅当前合同管理权限可调用。
|
||||
- 只允许持久状态 `active`(包含页面派生 expired);draft 应删除/取消拟办,archived 与 terminated 拒绝。
|
||||
- 在一个 Prisma transaction 中锁定/读取当前行、校验状态、写 `status + terminated_*`、创建 `CONTRACT_REGISTRY_TERMINATED` 审计。
|
||||
- 审计创建失败时整个事务回滚;不调用现有会吞错的 best-effort helper。
|
||||
- 审计 extra 只记录合同编号、from/to 状态和必要原因摘要,不写合同正文或原始 payload。
|
||||
|
||||
### 4.3 归档、恢复与修正
|
||||
|
||||
- 现有归档/恢复 UI 迁入详情。服务端至少阻止 archived 普通 PATCH 和 terminated 的任何一般状态写入。
|
||||
- 归档/恢复属于状态变更,改为事务内 required audit;不得状态成功而审计失败。
|
||||
- 归档后修正使用追加式 correction 记录,不覆盖主合同普通字段。可复用 `AuditLog` 新 action 与白名单修正 payload;详情把它显示为追加历史。
|
||||
- 续约保持 active/expired → 新 draft + 原合同 archived,并继续事务内审计。
|
||||
|
||||
## 5. 前端信息架构
|
||||
|
||||
### 5.1 路由与页面
|
||||
|
||||
- 列表:`/contracts-registry`
|
||||
- 详情:`/contracts-registry/:contractId`
|
||||
- 新建可使用独立 `/contracts-registry/new` 或明确 Dialog;实施时优先选择复用现有表单且 dirty guard 最清晰的独立新建工作区。
|
||||
- 编辑在详情工作区展开或进入明确编辑态,不再使列表页面整体变形。
|
||||
|
||||
### 5.2 信息顺序
|
||||
|
||||
1. 状态与下一步。
|
||||
2. 业务摘要:编号、类型、相对方、金额、日期、负责人、敏感级别。
|
||||
3. 证据区:归档位置、离线档案号、涉密资料索引;显示“涉密资料请登记线下归档位置”。
|
||||
4. 变更/操作历史。
|
||||
5. 动作面板:PC 右侧,窄屏/移动底部安全区,复用低风险子任务的共享动作容器。
|
||||
|
||||
不渲染空附件模块,不在列表增加附件完整性列。版式分区保持可扩展,但不显示六类空占位。
|
||||
|
||||
## 6. 动作矩阵
|
||||
|
||||
| 状态 | 编辑 | 续约 | 归档 | 恢复 | 终止 | 修正追加 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| draft | 允许 | 禁止 | 按现有规则 | - | 禁止 | 禁止 |
|
||||
| active / expired | 允许 | 允许 | 允许 | - | 允许 | 禁止 |
|
||||
| archived | 禁止覆盖 | 禁止 | - | 允许 | 禁止 | 授权角色允许 |
|
||||
| terminated | 禁止 | 禁止 | 禁止 | 禁止 | 禁止 | 只读 |
|
||||
|
||||
终止面板始终显示至少 10 字要求;不足时按钮禁用且邻近显示原因。确认弹窗展示合同编号、相对方、金额以及“合同将标记已终止、编号不释放、普通编辑/续约不可用”的后果。
|
||||
|
||||
## 7. 跨模块与权限
|
||||
|
||||
- 本轮详情只稳定展示已有 project 关联并跳转项目详情。
|
||||
- 采购/付款没有现成合同外键或可靠查询时不显示空模块,不为页面完整性新增错误关联。
|
||||
- 合同审批和付款写动作不进入详情;未来只显示授权摘要与归属模块入口。
|
||||
- 所有详情数据由服务端范围查询裁剪,前端隐藏不替代权限。
|
||||
|
||||
## 8. 迁移、发布与回滚
|
||||
|
||||
- 本地:先 `prisma:validate`,生成 client,运行聚焦 route 测试和迁移状态只读检查;不得 reset 数据库。
|
||||
- migration 是向后兼容的可空列。部署新 schema 后、首次产生 terminated 行前,旧应用可暂时忽略新列;不主动 drop。
|
||||
- 一旦生产产生 terminated 行,旧应用不认识该状态且无法展示终止事实,应用回滚应以 roll-forward 或兼容 hotfix 为主,不能简单回到旧客户端/服务端。
|
||||
- drop 三列会保留审计但使 terminated 行与字段不变量失配,因此不作为自动回滚。任何生产 schema drop、状态翻译或数据修复都需另行批准。
|
||||
- 部署后显式运行 migration status,并验证 `/ready`、合同列表/详情、终止负向路径和权限;init 日志不能替代迁移状态证据。
|
||||
|
||||
## 9. 只读 Review
|
||||
|
||||
实施后 reviewer 只读检查:
|
||||
|
||||
- terminated 与三字段全有/全空不变量。
|
||||
- 通用 create/PATCH 无法绕过专用终止接口。
|
||||
- audit failure 能回滚状态写入。
|
||||
- expired/expiring/renew 行为未误含 terminated。
|
||||
- `terminated_by` Restrict 外键和 migration 兼容。
|
||||
- 详情 403/404、document refs 和 history 不泄露敏感字段。
|
||||
- archived 锁定与修正追加不覆盖主记录。
|
||||
|
||||
reviewer 不修改代码,发现问题由主会话修复后再复查。
|
||||
@@ -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,66 @@
|
||||
# 实施计划
|
||||
|
||||
## 1. Schema 与迁移
|
||||
|
||||
- [ ] 在 `ContractRegistry` 增加 `terminated_at/reason/by`、terminator 关系,在 `Employee` 增加反向关系。
|
||||
- [ ] `terminated_reason` 限制 1000,`terminated_by` 使用 `onDelete: Restrict`;按查询需要添加索引。
|
||||
- [ ] 生成纯增量 migration,检查只有可空列、外键/索引,无历史回填、drop 或状态改写。
|
||||
- [ ] 运行 `cd server && npm run prisma:format && npm run prisma:validate && npm run prisma:generate`。
|
||||
|
||||
## 2. 状态与服务端不变量
|
||||
|
||||
- [ ] 持久/筛选状态加入 `terminated`,一般 create/PATCH 可写状态仍排除 terminated。
|
||||
- [ ] 增加 terminated 筛选、effective status、expiring 排除和 renew 拒绝测试。
|
||||
- [ ] 增加详情 GET,按列表相同范围查询 owner/project/terminator/document refs,并投影安全 history。
|
||||
- [ ] 增加专用 terminate schema 和接口,reason trim 后 10–1000。
|
||||
- [ ] 终止 transaction 原子写三字段、status 和 required audit;模拟 audit failure 证明全回滚。
|
||||
- [ ] 阻止 archived/terminated 普通字段覆盖与终止绕过。
|
||||
- [ ] 归档/恢复改为 required audit;实现归档后追加修正记录,不覆盖主合同。
|
||||
- [ ] 保持续约 active/expired 语义和事务审计。
|
||||
|
||||
## 3. 客户端 API 与状态映射
|
||||
|
||||
- [ ] 客户端合同类型加入 `terminated` 与三个终止字段、terminator、document refs、history DTO。
|
||||
- [ ] `contractStatusLabel` 增加“已终止”,未知兜底为“未知状态”而非原值。
|
||||
- [ ] 增加 fetch detail、terminate、correction 等必要 API 函数,不暴露 raw audit/document path。
|
||||
|
||||
## 4. 列表、详情和表单
|
||||
|
||||
- [ ] 合同列表移除内联 editor,仅保留筛选、提醒、导出、状态和查看详情。
|
||||
- [ ] 新建合同详情路由和页面元数据,权限 guard 与现有列表一致。
|
||||
- [ ] 建设状态/下一步、摘要、证据区、历史和动作面板;窄屏复用共享底部动作容器。
|
||||
- [ ] 证据区展示归档位置、离线档案号、涉密资料索引和强制引导文案;不显示空附件区或附件完整性列。
|
||||
- [ ] 迁移编辑、续约、归档/恢复;按动作矩阵隐藏或禁用。
|
||||
- [ ] 终止输入持续显示至少 10 字要求;禁用时显示原因;确认包含合同编号、相对方、金额和后果。
|
||||
- [ ] 归档后只提供授权修正追加入口,不覆盖普通字段。
|
||||
- [ ] 只展示真实 project 摘要与跳转;采购/付款无稳定关联时不渲染。
|
||||
|
||||
## 5. 后端与迁移验证
|
||||
|
||||
- [ ] `cd server && node --check src/routes/contractsRegistry.js`
|
||||
- [ ] `cd server && npm test -- tests/unit/routes/contractsRegistry.test.js tests/unit/routes/exports.contractsRegistry.test.js`
|
||||
- [ ] `cd server && npm run prisma:validate`
|
||||
- [ ] `cd server && npx prisma migrate status`(只读;不得未经批准 deploy/reset)。
|
||||
- [ ] 检查生成 migration SQL 和既有数据兼容,记录应用回滚前/后首次终止写入的不同边界。
|
||||
|
||||
## 6. 前端与交互验证
|
||||
|
||||
- [ ] `cd client && npm run typecheck`
|
||||
- [ ] `cd client && npm run build`
|
||||
- [ ] 增加合同详情聚焦 Playwright:列表进入、深链刷新、无权限/不存在、dirty guard、涉密引导、无附件区。
|
||||
- [ ] 覆盖终止原因不足、确认摘要、成功状态、重复终止、审计失败和窄屏动作安全区。
|
||||
- [ ] 覆盖归档锁定、修正追加、恢复、续约和未知状态兜底。
|
||||
|
||||
## 7. 只读 Review 与治理
|
||||
|
||||
- [ ] 实施完成后启动只读 reviewer,按 design 第 9 节逐项审查,明确 reviewer 不改代码。
|
||||
- [ ] 主会话修复 finding 后重新运行聚焦测试并请求只读复查。
|
||||
- [ ] 运行 task validate、process contract、docs registry、Trellis governance、敏感内容检查和 `git diff --check`。
|
||||
- [ ] 判断稳定规则是否需要更新现有合同规约或 Trellis spec,不新建平行文档。
|
||||
|
||||
## 8. 提交、部署与回滚
|
||||
|
||||
- [ ] 使用中文 commit message 并引用 `08-05-contract-detail-workspace`。
|
||||
- [ ] 本地实现提交不等于批准生产 migration 或部署。
|
||||
- [ ] 生产发布另行获批后,显式验证 migration status、`/ready`、合同详情和权限负向路径。
|
||||
- [ ] 首次生产终止写入前保留应用回滚窗口;产生 terminated 行后优先 roll-forward,不自动 drop 列或回退到不识别 terminated 的旧应用。
|
||||
@@ -0,0 +1,117 @@
|
||||
# 合同详情页与高风险动作归位
|
||||
|
||||
## Goal
|
||||
|
||||
独立建设符合“列表查找、详情处理”的合同工作区,明确合同详情的信息顺序、权限边界、证据表达、终止状态和高风险动作位置;不把从零新增详情能力混入采购/资产的路由搬迁。
|
||||
|
||||
## Background
|
||||
|
||||
- 当前 `ContractsRegistryPage` 是“列表 + 内联新建/编辑器”,只有 `showEditor/editorId`,没有 selected ID、`?id=` 详情或独立详情态。
|
||||
- 当前客户端只有合同列表查询、创建、PATCH、续约和台账导出;服务端没有单条详情 GET。
|
||||
- 当前持久状态只有 `draft/active/archived`,`expired` 是按到期日推导的展示状态;Prisma `status` 为 `VarChar(32)`,新增 `terminated` 本身无需改列类型或回填历史状态。
|
||||
- 当前合同审计为 best-effort:写入失败只记录服务日志而不阻断业务。终止属于高风险状态变更,原因和审计不得沿用该失败策略。
|
||||
- 合同可关联项目;`ConfidentialDocumentRef` 可关联合同,但它是涉密资料索引,不是正文文件库。
|
||||
- `docs/需求-合同附件归档规约.md` 已规划非涉密附件受控入库,并把它列为详情页之后的独立开发步骤。本轮不做附件能力是范围裁剪,不是永久产品边界。
|
||||
|
||||
## Scope
|
||||
|
||||
- 合同列表改为纯查找入口,详情承担状态、摘要、证据、历史和动作面板。
|
||||
- 证据区本轮只承载归档位置、离线档案号和涉密资料索引;非涉密附件能力按 `docs/需求-合同附件归档规约.md` 第 13 节第 4 项另建 task。
|
||||
- 增加 `terminated` 及 `terminated_at / terminated_reason / terminated_by` 三列,终止原因至少 10 个字,状态/终止字段/审计事务一致提交。
|
||||
- 迁移、状态机、权限和审计变更实施后必须完成只读 reviewer;生产 migration 和部署另行获批。
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1 页面与信息结构
|
||||
|
||||
- 合同列表只负责查找、筛选、状态摘要、到期/续签提醒、台账导出和进入详情;现有内联新建/编辑器随详情工作区落地移除。
|
||||
- 合同详情信息顺序固定为:`状态与下一步 → 业务摘要 → 证据区 → 变更/操作历史 → 动作面板`。
|
||||
- PC 动作面板位于详情右侧;移动端如本轮提供详情表达,只保留查看和底部安全区轻动作,不复制 PC 管理能力。
|
||||
- 建立稳定合同详情路由、单条 GET 合同和客户端 detail query;服务端沿用当前合同范围权限,不扩大普通用户可见范围。
|
||||
|
||||
### R2 证据区本轮范围裁剪
|
||||
|
||||
- 本轮证据区只展示:归档位置 `archive_location`、离线档案号 `offline_archive_no`、涉密资料索引 `ConfidentialDocumentRef`。
|
||||
- 必须显示业务引导:“涉密资料请登记线下归档位置”,解释 `archive_location` 非空必填的原因。
|
||||
- 版式为后续附件类型分区预留扩展位置,但本轮不渲染空附件区,也不在列表显示“附件完整性”列。
|
||||
- 本轮不新增合同正文或非涉密附件的上传、预览、下载与附件审计;该能力按 `docs/需求-合同附件归档规约.md` 第 13 节第 4 项另建 Trellis task。
|
||||
- 不得把“不在本轮上传合同文件”写成永久产品边界。
|
||||
|
||||
### R3 状态模型
|
||||
|
||||
- 本轮状态值域为 `draft / active / terminated / archived / expired`,其中 `expired` 继续由日期推导,`terminated` 为新增持久状态,用户文案为“已终止”。
|
||||
- “作废”不新增另一状态;已编号或已生效合同不再执行时统一进入“已终止”,合同编号不得释放或复用。
|
||||
- `terminated` 采用专用字段 `terminated_at / terminated_reason / terminated_by`;终止原因 trim 后至少 10 个字,三项在详情状态区可直接读取。
|
||||
- 状态不变量固定为:`status = terminated` 当且仅当三个终止字段均非空;其他状态三个字段均为空。服务层在事务内强制该不变量。
|
||||
- `terminated_by` 关联员工并使用 `onDelete: Restrict`,终止人不得因员工记录变化丢失。
|
||||
- 已签署、履行中、已完成等状态后置;真实使用证明现有字段不足时另建 task。
|
||||
- 归档后普通字段锁定;授权修正只能追加变更记录,不覆盖历史事实。
|
||||
|
||||
### R4 动作矩阵
|
||||
|
||||
- 编辑:常规动作,按当前管理权限和状态开放。
|
||||
- 续约:常规动作,只在既有允许状态开放,沿用新草稿 + 原合同归档语义。
|
||||
- 归档/恢复:状态动作;归档后锁定普通字段,恢复及修正必须留痕。
|
||||
- 终止:高风险动作;原因至少 10 个字,输入区持续显示要求,按钮禁用时明确显示“请填写终止原因,至少 10 个字”;二次确认展示合同编号、相对方、金额和后果,状态写入、三个终止字段与审计必须同一事务成功。
|
||||
- 本轮不在合同详情放审批通过、驳回、退回或转审;如后续启用合同审批,只展示状态和进入统一审批中心的入口。
|
||||
- 本轮不在合同详情放付款写动作;只展示授权付款摘要和“查看付款”入口。
|
||||
- 台账导出继续属于列表筛选范围动作,不变成单条合同“下载”。
|
||||
|
||||
### R5 关联、历史与权限
|
||||
|
||||
- 跨模块只做“摘要 + 跳转”:项目、采购和付款只展示当前 API/关联模型能证明且当前用户有权查看的摘要;无稳定关联字段时不虚构数据。
|
||||
- 详情展示合同创建、编辑、状态变化、归档/恢复、续约、终止和归档后修正历史;高风险写操作不得因审计失败而静默成功。
|
||||
- 普通用户无权访问时不泄露合同是否存在;管理员和授权角色仍受服务端范围约束。
|
||||
- 深链、刷新、返回、无权限、不存在和 dirty-form 离开均有可解释结果。
|
||||
|
||||
### R6 数据迁移、审查与发布
|
||||
|
||||
- 新增三个可空字段和 `terminated_by` Restrict 外键的 Prisma migration;历史合同不回填终止事实,新增列允许空值,仅终止接口可原子写入完整三项。
|
||||
- migration 必须经过 `prisma:validate`、聚焦路由/服务测试、迁移状态检查及既有数据兼容验证;禁止用数据库重置代替迁移。
|
||||
- 实施完成后启动只读 reviewer,重点检查状态机、权限、事务审计、迁移兼容和隐私;reviewer 不修改代码。
|
||||
- 生产部署需另行明确批准;部署后必须显式验证 migration 状态、`/ready`、合同列表/详情和权限负向路径,不能只依赖 runbook 或 init 成功日志;失败时停止并按设计回滚。
|
||||
- migration 为纯增量可空列;代码回滚前如已产生终止记录,先保留审计追溯并评估字段信息损失,再决定是否 drop 三列,不自动执行破坏性 schema 回滚。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 合同列表不再承载记录编辑器,只保留查找、筛选、提醒、导出和进入详情。
|
||||
- [ ] 合同详情具有稳定深链,并按“状态与下一步、业务摘要、证据区、历史、动作面板”展示。
|
||||
- [ ] 证据区展示归档位置、离线档案号和涉密资料索引,并出现“涉密资料请登记线下归档位置”;不渲染空附件区或列表附件完整性列。
|
||||
- [ ] 本轮没有新增合同正文/非涉密附件上传、预览或下载;PRD 明确其为后续独立能力而非永久禁止。
|
||||
- [ ] 状态值域支持 `terminated`;终止原因至少 10 个字,操作者和时间可在详情直接读取,合同编号不释放。
|
||||
- [ ] 服务层与聚焦测试证明 `terminated` 与三个终止字段满足全有或全空不变量,`terminated_by` 使用 Restrict 外键。
|
||||
- [ ] 终止原因不足 10 字时界面邻近显示禁用原因;满足要求后,确认展示合同编号、相对方、金额和后果,状态、三字段与审计同事务成功或共同失败。
|
||||
- [ ] 归档后普通字段锁定;授权修正追加记录,不覆盖原历史。
|
||||
- [ ] 编辑、续约、归档/恢复按权限和状态显示;审批类与付款类写动作不进入合同详情。
|
||||
- [ ] 项目/采购/付款只做有权限且有真实数据来源的摘要与跳转,不复制对方流程。
|
||||
- [ ] 无权限详情请求不泄露合同事实;不存在、深链刷新、返回和 dirty-form 行为可解释。
|
||||
- [ ] 如包含 migration,Prisma 校验、迁移兼容、聚焦后端测试、前端 Playwright、typecheck/build 和 Trellis 门禁通过。
|
||||
- [ ] 只读 reviewer 完成状态机、权限、事务审计、迁移与隐私审查,且未修改代码。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 本轮不做非涉密附件上传、预览、下载或附件审计;按合同附件归档规约第 13 节第 4 项另建 task。
|
||||
- 不接入合同审批;如需要按规约第 7 节另建 task 接入统一审批中心。
|
||||
- 不做电子签章、法务审查、条款库、版本协同或完整 CLM。
|
||||
- 不新增已签署、履行中、已完成等尚缺事实字段支撑的状态。
|
||||
- 不复制采购、资产、付款或项目完整流程。
|
||||
- 不写入生产合同正文、真实附件、完整归档路径或真实业务数据。
|
||||
|
||||
## Accepted Risk
|
||||
|
||||
- “已终止”是不可逆终态,本轮不提供恢复、续约或再归档路径。必填至少 10 个字的原因、合同编号/相对方/金额/后果确认和事务审计用于降低误操作风险;若后续出现真实误终止案例,另建 task 评估受控管理员修正路径。
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 无;需求范围与终止持久化方案均已确认。
|
||||
|
||||
## Test Plan
|
||||
|
||||
- `cd server && npm run prisma:format && npm run prisma:validate && npm run prisma:generate`
|
||||
- `cd server && node --check src/routes/contractsRegistry.js`
|
||||
- `cd server && npm test -- tests/unit/routes/contractsRegistry.test.js tests/unit/routes/exports.contractsRegistry.test.js`
|
||||
- `cd server && npx prisma migrate status`(只读;不得用 reset 或未经批准 deploy)。
|
||||
- `cd client && npm run typecheck && npm run build`
|
||||
- 聚焦 Playwright 覆盖合同列表/详情、证据引导、终止原因禁用提示、确认摘要、状态成功/重复终止、归档锁定、权限负向和窄屏动作。
|
||||
- 实施后启动只读 reviewer,检查状态不变量、专用终止接口、事务审计失败回滚、`terminated` 不参与过期/续约、Restrict 外键、迁移兼容和敏感字段裁剪。
|
||||
- 运行 task validate、process contract、docs registry、Trellis governance、敏感内容检查和 `git diff --check`。
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"id": "contract-detail-workspace",
|
||||
"name": "contract-detail-workspace",
|
||||
"title": "合同详情页与高风险动作归位",
|
||||
"description": "独立规划合同详情信息结构、权限边界、证据区和高风险动作归位,不与采购/资产路由搬迁混做。",
|
||||
"status": "planning",
|
||||
"dev_type": null,
|
||||
"scope": "server-contract-schema-api-and-client-detail",
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shunli",
|
||||
"assignee": "shunli",
|
||||
"createdAt": "2026-08-05",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-05-product-interaction-consistency",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {
|
||||
"description": "建设合同详情工作区并新增可审计的 terminated 状态、终止字段和事务审计,范围裁剪附件能力。"
|
||||
}
|
||||
}
|
||||
@@ -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,57 @@
|
||||
# 设计说明
|
||||
|
||||
## 1. 目标与约束
|
||||
|
||||
本任务只处理不改变业务 URL、API、数据库、权限模型和状态机的交互一致性。允许修改共享前端工具与组件,但不得引入新的页面体系或全站视觉重做。
|
||||
|
||||
## 2. 错误消息收敛
|
||||
|
||||
- `client/src/lib/resolveErrorMessage.ts` 成为普通用户错误消息的默认入口:API error 只返回 `mapApiErrorMessage(...)` 的自然中文,不拼 code。
|
||||
- 页面内重复的 `isApiError + mapApiErrorMessage` helper 逐步改为共享入口;本任务覆盖当前搜索到的 58 个文件。
|
||||
- 管理、审计、排障页确需 code 时使用显式技术明细函数或独立明细区,不把 code 混进普通 toast/红条文本。
|
||||
- 增加源码合同测试或 QA 搜索,阻止普通消息重新出现 `error.code + ':'` 模式。
|
||||
- 失败反馈位置按可恢复性决定:字段/材料问题就近、区域加载失败驻留在区域并可重试、短暂成功 toast;本轮不全量迁移所有历史 toast。
|
||||
|
||||
## 3. 状态与命名
|
||||
|
||||
- `StatusBadge` 未知状态统一显示“未知状态”;业务域需要更具体文案时显式传 `label` 或使用域映射,不扩充一个无边界的全局状态表。
|
||||
- 员工在职阶段使用独立中文映射,避免 `employmentStatus` 原样透出。
|
||||
- `TodosPage` 状态筛选和徽章使用自然中文。
|
||||
- 审批任务、个人事项、聚合面板分别使用“待我审批 / 我的事项 / 今日待处理”;内部路由和 API 字段保持不变。
|
||||
|
||||
## 4. 首页与首次欢迎
|
||||
|
||||
- PC 页面顺序调整为 `SummaryStrip → TodayActionPanel → QuickEntryPanel → secondary`。
|
||||
- `PortalHomePage` 已拥有权限、角色能力和 quick entries;WelcomeDialog 从调用方接收经过权限过滤的入口,避免在弹窗内复制完整权限模型。
|
||||
- 移动首页移除常驻考勤说明卡;财务第三指标改为真实数据或删除该 tile,不用静态 `OA` 填充。
|
||||
- 无真实数据的区域不渲染,保留必要空态和真实下一步。
|
||||
|
||||
## 5. 审批动作解释
|
||||
|
||||
- 退回说明“返回可修改环节/发起人补充后可继续”,驳回说明“本次流程终止或按规则重新发起”;最终措辞必须对应现有服务端 action 语义,不改变 action。
|
||||
- 转审理由输入区显示“至少 10 个字”和实时未满足原因;禁用按钮不能成为唯一反馈。
|
||||
- 费用控制按超标事实、是否阻止通过和下一步表达,不显示 `controlMode` 字段式文案。
|
||||
|
||||
## 6. 共享移动底部动作
|
||||
|
||||
- 从 `BusinessEditTemplate`、移动报销、移动审批和付款响应式页面提取最小共享容器,统一 fixed surface、安全区、内容底部留白和主次动作布局。
|
||||
- 共享组件只接收动作内容、是否显示和测试 ID,不承载报销/审批/付款状态机。
|
||||
- 先迁移至少移动报销与移动审批;付款页仅在无业务风险且结构相同时迁移,否则保留并记录后续项。
|
||||
|
||||
## 7. 差旅冻结模块缺陷
|
||||
|
||||
- 删除 `TravelRequestsPage` 在无创建权限时自动选择第一条记录的 effect。
|
||||
- 未带 `id` 时保持列表/空详情;显式选择后的既有 `?id=` 详情不变。
|
||||
- 不改变导航、API、创建能力、状态机或恢复差旅主线。
|
||||
|
||||
## 8. 可访问性与兼容
|
||||
|
||||
- 本任务触及的输入具有关联 label、键盘可达和焦点可见性;错误出现在关联字段/区域附近。
|
||||
- 聚焦页面可增加 axe 或等价语义断言,但不把全站历史债务作为本轮阻断。
|
||||
- 错误文案变化不改变 API code 本身;日志、服务端响应和审计数据保持原样。
|
||||
|
||||
## 9. 回滚
|
||||
|
||||
- 错误消息工具、移动动作容器和页面文案分批提交,任一共享改动可连同消费者同批回滚。
|
||||
- 差旅只删除一个 effect,回滚不会修改数据;若回滚则恢复旧自动选择行为。
|
||||
- 无 schema、API 或生产数据回滚步骤。
|
||||
@@ -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,62 @@
|
||||
# 实施计划
|
||||
|
||||
## 1. 共享错误入口
|
||||
|
||||
- [x] 修改 `resolveErrorMessage` 默认去除 code,并为确需技术 code 的管理/审计场景提供显式分离能力。
|
||||
- [x] 迁移当前 58 个文件中的普通用户消息拼接,保留 API code 作为控制流判断而非展示文本。
|
||||
- [x] 增加源码/组件回归,覆盖 Portal、审批、报销、移动页和管理例外边界。
|
||||
|
||||
## 2. 状态与命名
|
||||
|
||||
- [x] `StatusBadge` 未知值显示“未知状态”;员工在职阶段显式映射中文。
|
||||
- [x] `TodosPage` 状态选项/徽章中文化。
|
||||
- [x] 将用户可见的审批任务、个人事项和聚合面板收敛为“待我审批 / 我的事项 / 今日待处理”,同步 PC/移动导航和测试断言。
|
||||
|
||||
## 3. 首页与 WelcomeDialog
|
||||
|
||||
- [x] 调整 PC 工作台内容顺序,今日待处理先于常用入口。
|
||||
- [x] WelcomeDialog 接收权限过滤后的入口,无审批权限时不显示审批入口。
|
||||
- [x] 删除移动考勤说明卡;财务 tile 只保留真实指标。
|
||||
|
||||
## 4. 审批与反馈
|
||||
|
||||
- [x] 为退回/驳回补后果说明,核对服务端动作语义不变。
|
||||
- [x] 转审理由显示至少 10 字要求和禁用原因。
|
||||
- [x] 费用控制改为业务后果表达。
|
||||
- [x] 本任务触及的字段错误、区域失败和提交失败按设计放置,输入与恢复入口不丢失。
|
||||
|
||||
## 5. 移动动作与差旅缺陷
|
||||
|
||||
- [x] 抽取共享移动底部动作容器并迁移移动报销、移动审批与 `BusinessEditTemplate`;统一安全区、内容留白和 pending/disabled 表达。
|
||||
- [x] 已评估付款响应式页;其动作条仍含付款批次专用响应式结构,本轮按设计保留为已记录例外,不强行迁移。
|
||||
- [x] 删除差旅自动选中第一条的 effect,保留显式 `?id=` 详情。
|
||||
|
||||
## 6. 可访问性与测试
|
||||
|
||||
- [x] 为本任务触及的表单补关联 label、键盘/焦点断言和邻近错误。
|
||||
- [x] 更新 `login-portal-ux.spec.ts`、`workflow-action-confirm-ui.spec.ts` 等既有断言,并新增 `interaction-pattern-unification.spec.ts` 覆盖共享错误、移动动作、状态、WelcomeDialog、工作台恢复和差旅缺陷合同。
|
||||
- [x] 运行 `cd client && npm run typecheck`。
|
||||
- [x] 运行 `cd client && npm run build`。
|
||||
- [x] 运行无浏览器源码合同 Playwright:`interaction-pattern-unification.spec.ts` 共 6 项通过。
|
||||
- [ ] 运行需要 Chromium 的聚焦浏览器回归:当前环境默认浏览器缺失,系统 Chromium 又因沙箱权限 `Operation not permitted` 无法启动,记为 `PAUSED`,不得宣称浏览器用例通过。
|
||||
- [x] 运行普通错误消息 code 前缀扫描,并人工核对 `AuditLogsPage` 为唯一技术明细例外。
|
||||
- [x] 运行 task validate、process contract、docs registry、Trellis governance 和 `git diff --check`;规范/事实源更新后最终复跑通过。
|
||||
|
||||
## 7. 提交与回滚
|
||||
|
||||
- [x] 提交前确认没有业务 URL、后端 API、schema、权限模型或真实数据变化;导航改动仅为已批准的入口文案与现有权限能力过滤。
|
||||
- [x] 使用中文 commit message 并引用 `08-05-interaction-pattern-unification`。
|
||||
- [x] 共享工具回滚时同步回滚消费者;移动审批、移动报销与 `BusinessEditTemplate` 不混用新旧容器,付款页旧实现按已记录例外保留。
|
||||
|
||||
## 8. 当前验证记录
|
||||
|
||||
- `cd client && npm run typecheck`:通过。
|
||||
- `cd client && npm run build`:通过。
|
||||
- `npx playwright test tests/e2e/interaction-pattern-unification.spec.ts --workers=1 --reporter=list`:6 passed。
|
||||
- `npx playwright test --list`:可发现 61 个文件、222 个测试。
|
||||
- `python3 ./.trellis/scripts/task.py validate 08-05-interaction-pattern-unification`:通过。
|
||||
- `node scripts/qa/check-process-contract.mjs --baseline --json`:`PASS`,0 errors / 0 warnings。
|
||||
- `node scripts/qa/check-trellis-governance.mjs`:`PASS`,0 errors / 0 warnings。
|
||||
- `node scripts/qa/audit-docs.mjs --check-registry`:0 errors;保留既有 22 个未登记文档 warning。
|
||||
- `git diff --check`:通过。
|
||||
- 需要真实 Chromium 的聚焦页面回归:`PAUSED`。阻塞来自当前执行环境的浏览器安装/沙箱权限,不作为产品用例失败,也不替代后续可运行环境中的浏览器验证。
|
||||
@@ -0,0 +1,76 @@
|
||||
# 低风险页面操作规范统一
|
||||
|
||||
## Goal
|
||||
|
||||
在不改变业务 URL、API、数据库、权限模型和状态机的前提下,统一高频页面的状态文案、首页信息优先级、权限可见性、审批动作解释、表单反馈和移动主动作,使用户在 PC 与移动端形成稳定预期。
|
||||
|
||||
## Confirmed Facts
|
||||
|
||||
- `StatusBadge` 未命中映射时直接显示原始 `status`,员工档案仍把 `employmentStatus` 直接传入,可能展示 `onboarding/probation/offboarding/inactive`。
|
||||
- PC 工作台仍渲染常用入口和今日待处理,需按首屏任务优先原则复核顺序;移动首页仍无条件展示考勤说明卡,财务第三个指标值仍为静态 `OA`。
|
||||
- `WelcomeDialog` 仍对所有用户硬编码“处理待办”等入口,未按审批权限过滤。
|
||||
- `/todos` 状态筛选仍直接展示 `todo/doing/done/cancelled`,且“待办”与审批“待我处理”的产品命名容易混淆。
|
||||
- 审批详情并列提供“退回/驳回”,但未解释后果差异;转审理由至少 10 字的门槛没有邻近说明。
|
||||
- 费用标准仍向审批人显示“管控模式:阻断/预警”内部配置词。
|
||||
- 页面提交失败既有 toast、页级红条和字段内联等多种方式,缺少按可恢复性划分的统一合同。
|
||||
- 移动报销、移动审批、付款响应式页面分别复制了固定底部动作容器;`BusinessEditTemplate` 已提供可复用实现线索。
|
||||
- 完整差旅已冻结,但 `TravelRequestsPage` 仍会在无创建权限且未选择记录时自动把第一条写入 `?id=`;修复该缺陷不恢复差旅能力。
|
||||
- 当前有 58 个前端文件在普通用户路径拼接 `CODE: 中文消息`;共享 `resolveErrorMessage` 也直接返回带 code 的文本,必须成为本轮统一收敛入口。
|
||||
|
||||
## Scope
|
||||
|
||||
- 仅修改前端共享错误/状态/动作组件、PC/移动高频页面文案与布局、WelcomeDialog 权限过滤、Todo 状态显示和差旅页面自动选中 effect。
|
||||
- 不改业务 URL、API、数据库、权限模型、审批/付款状态机或生产配置。
|
||||
- 差旅只删除自动选择第一条记录的缺陷,不恢复冻结能力;合同、采购/资产路由迁移由其他子任务负责。
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1:状态展示必须使用业务域映射;公共 `StatusBadge` 未知值显示“未知状态”,不得把技术枚举原样呈现给普通用户。
|
||||
- R2:PC 工作台首屏把真实“今日待处理”置于常用入口之前;无待办时仍保持自然空态,不制造说明卡。
|
||||
- R3:移动首页删除无真实数据的常驻考勤说明卡;财务指标只展示真实可比较数据,不能用 `OA` 占据数值位。
|
||||
- R4:首次欢迎入口按当前用户权限与已启用能力生成;不得引导用户进入无权访问的审批页。
|
||||
- R5:命名统一为“待我审批”(审批任务)、“我的事项”(个人 todo)和“今日待处理”(聚合面板);路由可保持兼容,不因文案治理改 URL。
|
||||
- R6:`todo/doing/done/cancelled` 等用户可见状态统一为自然中文。
|
||||
- R7:审批详情明确说明“退回”和“驳回”的业务后果;转审理由在输入区邻近显示至少 10 字要求及未满足原因。
|
||||
- R8:把“阻断/预警”等配置词转为审批人可执行的业务表达,不暴露内部控制字段名。
|
||||
- R9:反馈合同固定为:短暂成功使用 toast;字段/材料问题就近内联;页面或区域加载失败在对应区域内持久显示并提供重试;不可恢复的提交失败不得只依赖会消失的 toast。
|
||||
- R10:抽取或扩展共享移动底部动作容器,统一安全区、内容留白、主次动作层级和 pending/disabled 表达;页面业务动作仍由各自状态矩阵决定。
|
||||
- R11:关键表单控件具有可关联 label、键盘可达性和清晰焦点;为登录、首页、审批详情、报销详情和移动报销详情中的本任务触点增加聚焦可访问性断言。
|
||||
- R12:保留当前中文错误映射并收敛唯一解析入口;普通用户路径只返回自然中文,不显示错误 code、接口名、路由栈、完整路径或原始 payload。管理员、审计和排障页如确需 code,只在明确技术明细区域最小展示,不复用普通消息字符串。
|
||||
- R13:优先复用现有 `ListWorkspace`、`BusinessListTemplate`、详情动作组件、错误映射和移动安全区样式,不新增第四套页面/反馈系统。
|
||||
- R14:删除差旅首屏自动选择第一条记录的 effect;用户未显式选择时保持纯列表/空详情状态,不改差旅路由、功能入口或业务能力。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 员工档案和本任务触及页面不显示未映射英文状态;未知状态显示“未知状态”。
|
||||
- [ ] PC 工作台首个业务内容区为“今日待处理”,常用入口后置;移动首页不再显示静态考勤说明卡或值为 `OA` 的指标。
|
||||
- [ ] 无审批权限用户的 WelcomeDialog 不出现“处理待办”入口,其他入口同样按权限/能力过滤。
|
||||
- [ ] PC/移动审批入口使用“待我审批”,个人 todo 使用“我的事项”,聚合区使用“今日待处理”;Todo 状态选项全部为中文。
|
||||
- [ ] 审批人无需猜测即可区分退回/驳回后果;转审理由不足 10 字时在输入区看到明确原因。
|
||||
- [ ] 费用控制提示说明超标事实、影响和下一步,不出现“管控模式”字段式表达。
|
||||
- [ ] 成功、字段错误、区域加载失败和提交失败分别遵循统一反馈合同;聚焦回归证明错误可恢复且输入不丢失。
|
||||
- [ ] 至少两个现有移动详情页复用共享底部动作容器,安全区和页面底部留白没有回退。
|
||||
- [ ] 本任务触及的 A 类页面具有 label/键盘/焦点回归;若引入 axe,仅限聚焦页面,不把全站历史债务变成本任务阻断。
|
||||
- [ ] 当前 58 个文件范围内的普通用户错误提示不再拼接内部 code;范围显式包含唯一收敛入口 `client/src/lib/resolveErrorMessage.ts`。代码搜索和聚焦页面回归证明没有同类前缀残留;`client/src/pages/AuditLogsPage.tsx` 作为 R12 允许的技术明细例外单独列出,不计入“无残留”判定。
|
||||
- [ ] 直接进入 `/travel-requests` 且未带 `id` 时不自动写入第一条记录;用户显式选择后既有 `?id=` 详情仍可用。
|
||||
- [ ] `cd client && npm run typecheck && npm run build`、聚焦 Playwright 与 Trellis 门禁通过。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 详情路由、新建页路由、后端 API、数据库或状态机修改。
|
||||
- 全站一次性迁移所有 toast、所有表单和所有页面骨架。
|
||||
- 付款、报销、审批等业务动作权限或服务端规则调整。
|
||||
- 冻结差旅的功能恢复、详情路由化、导航调整或能力扩展。
|
||||
- 模块冻结/下线、导航产品组合调整和暗色模式。
|
||||
|
||||
## Test Plan
|
||||
|
||||
- `cd client && npm run typecheck`
|
||||
- `cd client && npm run build`
|
||||
- 聚焦 Playwright:工作台首屏与 WelcomeDialog 权限、审批动作解释、移动首页/底部动作、Todo 中文状态、差旅未自动选中。
|
||||
- 运行普通用户错误前缀扫描;按当前 58 个文件范围核对残留,显式包含 `client/src/lib/resolveErrorMessage.ts`,并将 `client/src/pages/AuditLogsPage.tsx` 作为允许的技术明细例外单独检查、不计入“无残留”判定。
|
||||
- 运行 `python3 ./.trellis/scripts/task.py validate 08-05-interaction-pattern-unification`、process contract、docs registry、Trellis governance 和 `git diff --check`。
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 无;本子任务可在父任务范围确认后进入设计。
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"id": "interaction-pattern-unification",
|
||||
"name": "interaction-pattern-unification",
|
||||
"title": "低风险页面操作规范统一",
|
||||
"description": "收敛共享错误消息、状态文案、首页优先级、审批反馈、移动动作容器和差旅自动选中缺陷,不改业务 API 或状态机。",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": "client-shared-interaction-and-feedback",
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shunli",
|
||||
"assignee": "shunli",
|
||||
"createdAt": "2026-08-05",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": "08-05-product-interaction-consistency",
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {
|
||||
"description": "收敛共享错误消息、状态文案、首页优先级、审批反馈、移动动作容器和差旅自动选中缺陷,不改业务 API 或状态机。"
|
||||
}
|
||||
}
|
||||
@@ -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,44 @@
|
||||
# 设计说明
|
||||
|
||||
## 1. 任务树与边界
|
||||
|
||||
父任务只维护共同目标、子任务边界、顺序和最终集成验收,不直接修改业务代码。
|
||||
|
||||
| 子任务 | 交付物 | 主要风险 |
|
||||
| --- | --- | --- |
|
||||
| `08-05-interaction-pattern-unification` | 错误/状态/文案/反馈/移动动作一致性,删除差旅自动选中 | 跨页面文案回归、共享组件影响 |
|
||||
| `08-05-business-detail-routing` | 采购、资产已有详情态迁到独立路由 | URL 兼容、返回、dirty guard |
|
||||
| `08-05-contract-detail-workspace` | 新建合同详情、终止状态、迁移和事务审计 | 状态机、权限、迁移、审计 |
|
||||
|
||||
## 2. 执行顺序
|
||||
|
||||
1. 先实施低风险统一,形成后续页面可复用的错误消息、状态兜底和移动动作模式。
|
||||
2. 再迁移采购/资产路由;该子任务只消费共享模式,不修改合同、差旅、付款或请假。
|
||||
3. 合同详情作为独立能力最后实施,必须单独批准其 schema、状态机与迁移计划。
|
||||
|
||||
父子关系不隐含自动授权。每个子任务都要独立执行 `task.py start`、验证、提交与归档。
|
||||
|
||||
## 3. 共同设计合同
|
||||
|
||||
- 页面职责以 `docs/产品设计规则.md` 为准,视觉细节在 `frontend-design` 不可用时保守复用现有组件。
|
||||
- 普通用户文案使用自然中文;技术 code、原始 payload、完整路径和敏感资料不进入普通页面。
|
||||
- 列表负责查找,详情负责处理;差旅、付款和请假按已确认例外保持现有业务边界。
|
||||
- 高风险动作必须有对象摘要、后果、理由和审计;服务端权限与状态校验始终为最终边界。
|
||||
- 所有面向用户可测改动只有在另行批准部署并通过公网检查后才能宣布线上可测。
|
||||
|
||||
## 4. 集成与回滚
|
||||
|
||||
- 三个子任务独立提交;任何后续任务失败时可回滚该子任务,不回滚已经验收的前序任务。
|
||||
- 路由兼容保留旧链接过渡,避免低风险任务和路由任务必须同时上线。
|
||||
- 合同 migration 与前端详情同批兼容发布;生产应用、迁移和数据写入另需授权。
|
||||
- 最终集成复核只检查跨任务一致性、链接、共享组件和文档事实,不重做每个子任务的全部测试。
|
||||
|
||||
## 5. 共同验证
|
||||
|
||||
- `cd client && npm run typecheck && npm run build`
|
||||
- 相关聚焦 Playwright 与后端 Vitest
|
||||
- `python3 ./.trellis/scripts/task.py validate <task>`
|
||||
- `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 @@
|
||||
{"_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,39 @@
|
||||
# 实施计划
|
||||
|
||||
## 1. 父任务职责
|
||||
|
||||
- [ ] 审核三个子任务的 PRD、design 和 implement,确认范围无重叠。
|
||||
- [ ] 按“低风险统一 → 采购/资产路由化 → 合同详情”顺序分别请求实施批准。
|
||||
- [ ] 不启动父任务作为业务代码实现目标;启动拥有下一交付物的子任务。
|
||||
|
||||
## 2. 子任务交付顺序
|
||||
|
||||
### 2.1 低风险操作规范统一
|
||||
|
||||
- [ ] 完成共享错误、状态、命名、首页、审批反馈、移动动作和差旅自动选择修复。
|
||||
- [ ] 通过其聚焦测试与完整前端构建,独立提交和归档。
|
||||
|
||||
### 2.2 采购与资产详情独立路由化
|
||||
|
||||
- [ ] 在低风险任务完成后迁移采购/资产详情路由与旧链兼容。
|
||||
- [ ] 验证所有仓库内入口、dirty guard 和浏览器返回,独立提交和归档。
|
||||
|
||||
### 2.3 合同详情页与高风险动作归位
|
||||
|
||||
- [ ] 单独实施详情 API/UI、`terminated`、迁移、事务审计和历史展示。
|
||||
- [ ] 完成状态机/权限/迁移只读 review;reviewer 不改代码。
|
||||
- [ ] 本地验证后独立提交;生产迁移与部署继续等待明确授权。
|
||||
|
||||
## 3. 父任务最终集成门禁
|
||||
|
||||
- [ ] 三个子任务验收结果与父 PRD 逐项对账。
|
||||
- [ ] 确认普通用户错误无技术前缀,采购/资产新链接稳定,合同状态/证据边界未漂移。
|
||||
- [ ] 确认差旅未恢复、付款/请假未路由化、非涉密合同附件未提前实现。
|
||||
- [ ] 运行父任务 validate、process contract、docs registry、Trellis governance 和 `git diff --check`。
|
||||
- [ ] 需要部署时另建或更新部署 task,获得明确授权后执行公网验证。
|
||||
|
||||
## 4. 回滚点
|
||||
|
||||
- 每个子任务提交即一个独立回滚点。
|
||||
- 父任务不合并 schema 回滚、URL 回滚和共享组件回滚为一个不可分割提交。
|
||||
- 合同迁移的破坏性 drop 不随应用回滚自动执行,按合同子任务设计单独判断。
|
||||
@@ -0,0 +1,63 @@
|
||||
# 产品交互与页面操作一致性治理
|
||||
|
||||
## Goal
|
||||
|
||||
以小团队中文工作台为目标,把用户跨页面反复使用的“进入详情、新建、返回、审批、错误恢复和移动主动作”收敛为可预测的操作模型,减少同一动作在不同模块中位置、文案、反馈和导航行为不一致造成的学习与误操作成本。
|
||||
|
||||
## Background
|
||||
|
||||
- 当前产品事实源已固定六类页面模板、台账首屏纯列表、详情证据结构、状态矩阵动作语义和移动端底部主动作等边界。
|
||||
- 当前实现仍并存 `ListWorkspace`、`BusinessListTemplate`、`PageHeader` 等页面骨架,并存在独立路由、查询参数主从、列表内联编辑和 Dialog 等多种相互竞争的操作方式。
|
||||
- 2026-08-05 的产品/人机交互审查提出三类工作:先治理低风险、跨页复用的操作规则;再迁移已有详情态的同构台账;合同详情作为新增能力独立规划。
|
||||
- 当前仍有 58 个前端文件在普通用户路径拼接 `CODE: 中文消息`,共享 `resolveErrorMessage` 也保留该前缀;低风险子任务必须统一收敛。
|
||||
- 历史已确认付款批次保持列表内显式选择后的详情形态,不新增独立详情路由;本任务不得推翻该业务决定。
|
||||
|
||||
## Scope
|
||||
|
||||
- 父任务只管理三个子任务的边界、顺序、验收和最终集成复核。
|
||||
- 低风险子任务负责跨页面文案、错误反馈、状态兜底、首页优先级、移动动作容器和差旅自动选中缺陷。
|
||||
- 采购/资产子任务负责两个已有详情态台账的独立路由迁移与旧链接兼容。
|
||||
- 合同子任务负责从零合同详情工作区、`terminated` 状态和事务审计设计/实施。
|
||||
- 付款批次、请假和完整差旅不做详情路由化;非涉密合同附件能力按后续规约任务处理。
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1:本父任务作为需求源和跨子任务验收入口,不直接承接大批量业务页面实现。
|
||||
- R2:子任务 `08-05-interaction-pattern-unification` 处理不改变业务 URL、数据库、API、权限模型和状态机的低风险一致性问题。
|
||||
- R3:子任务 `08-05-business-detail-routing` 只处理采购、资产两个已有详情态台账的列表/详情路由边界、深链、返回和未保存保护。
|
||||
- R4:子任务 `08-05-contract-detail-workspace` 独立定义合同详情的信息结构、权限与高风险动作归位,不与采购/资产的路由搬迁混做。
|
||||
- R5:视觉与交互分析优先遵循项目指定的 `frontend-design`;本会话该 skill 不可用时,只以 `ui-ux-pro-max`、现有共享组件和产品事实源做保守补充,不创建新的视觉体系。
|
||||
- R6:不得借一致性治理扩张业务模块、重做既有状态机、改变企业微信/OA 边界、放宽权限或触碰真实数据。
|
||||
- R7:完整差旅属于冻结模块,不做详情路由化;仅在低风险子任务中删除自动选择第一条记录的缺陷,不恢复或扩展差旅能力。
|
||||
- R8:三个子任务独立实施与批准;低风险统一先行,采购/资产路由化随后,合同详情在其独立规划获批后另行实施。
|
||||
- R9:每个子任务独立完成本地验证、可访问性/交互回归和 Trellis 门禁;只有部署与公网关键路径检查通过后,才能告知用户线上可测。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 三个子任务均具有独立、可观察且不互相重复的验收标准。
|
||||
- [ ] 低风险统一先于详情路由化完成,路由化复用已经收敛的文案、反馈和动作组件。
|
||||
- [ ] 当前仍存在的问题被纳入范围;错误码前缀按覆盖变量名的完整搜索结果治理,不因错误的窄模式搜索被误判为已修复。
|
||||
- [ ] 付款批次继续使用现有显式选择详情形态,不新增独立详情路由。
|
||||
- [ ] 完整差旅继续冻结;只删除首屏自动选中第一条记录的缺陷。
|
||||
- [ ] 合同详情的信息结构和高风险动作边界在独立子任务中裁决,不作为采购/资产迁移的附带工作。
|
||||
- [ ] 不改变 API、数据库、权限模型、审批/付款状态机、生产配置或真实业务数据。
|
||||
- [ ] 前端 typecheck/build、聚焦 Playwright、Trellis governance、process contract、文档注册表和 `git diff --check` 均通过。
|
||||
- [ ] 每个子任务完成后有独立提交和回滚点,最终由父任务做跨子任务集成复核。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 模块冻结清单、一级导航产品组合和功能下线。
|
||||
- 全站视觉换肤、品牌重做、暗色模式或新设计系统。
|
||||
- 后端业务规则、权限、状态机、数据库 schema 或生产数据修改。
|
||||
- 付款批次独立详情路由。
|
||||
- 公网部署、生产配置或真实数据写入;如后续需要,必须另行获批。
|
||||
|
||||
## Test Plan
|
||||
|
||||
- 每个子任务先运行自身聚焦测试、typecheck/build 和任务 validate,再运行父任务集成门禁。
|
||||
- 父任务最终运行 `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`。
|
||||
- 路由子任务覆盖采购/资产深链、旧 `?id=` 兼容、返回与未保存保护;合同子任务额外覆盖 Prisma 校验、迁移状态、状态不变量、事务审计失败回滚和只读 reviewer。
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 无;三个子任务的边界和合同终止持久化方案均已确认。
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"id": "product-interaction-consistency",
|
||||
"name": "product-interaction-consistency",
|
||||
"title": "产品交互与页面操作一致性治理",
|
||||
"description": "统一小团队 OA 的页面操作模型,按低风险交互、采购资产路由、合同详情三项独立交付并完成集成验收。",
|
||||
"status": "planning",
|
||||
"dev_type": null,
|
||||
"scope": "frontend-governance-and-three-child-integration",
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shunli",
|
||||
"assignee": "shunli",
|
||||
"createdAt": "2026-08-05",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [
|
||||
"08-05-interaction-pattern-unification",
|
||||
"08-05-business-detail-routing",
|
||||
"08-05-contract-detail-workspace"
|
||||
],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {
|
||||
"description": "统一小团队 OA 的页面操作模型,按低风险交互、采购资产路由、合同详情三项独立交付并完成集成验收。"
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户