收口报销详情返回与金额展示(07-27-yang-jun-reimbursement-readiness)

这个提交包含在:
2026-08-01 15:03:48 +08:00
父节点 85fad71070
当前提交 3bfcbbfc91
共修改 16 个文件,包含 219 行新增和 81 行删除
@@ -92,6 +92,52 @@ const displayed = value.slice(0, 19).replace("T", " ");
const displayed = formatDisplayDateTime(value);
```
## Scenario: Shared RMB Display
### 1. Scope / Trigger
- Trigger: a reimbursement, invoice, fee-control or payment UI displays a CNY amount received as a string, number or nullable field.
### 2. Signatures
- Shared formatter: `formatMoney(value: string | number | null | undefined): string` in `client/src/lib/displayFormat.ts`.
### 3. Contracts
- Reuse the shared formatter across PC and mobile consumers; do not add page-local `Intl.NumberFormat` helpers for the same amount.
- Valid amounts display the CNY symbol, thousands separators and exactly two decimal places. Missing, empty or non-finite values display `-`; they must not become `¥0.00`.
### 4. Validation & Error Matrix
- `1860` / `"1860"` -> `¥1,860.00`.
- `"550.5"` -> `¥550.50`.
- `null`, `""`, invalid text or a non-finite number -> `-`.
### 5. Good/Base/Bad Cases
- Good: a PC detail total, mobile detail total and invoice summary all use `formatMoney`.
- Base: a real zero amount displays `¥0.00`.
- Bad: use string interpolation such as `` `¥${amount}` `` or coerce a missing amount with `Number(value ?? 0)`.
### 6. Tests Required
- Focused frontend regression covers integer, fractional and thousands-separated amounts on PC and mobile.
- Run `cd client && npm run typecheck && npm run build`.
### 7. Wrong vs Correct
#### Wrong
```tsx
<span>¥{detail.totalAmount ?? "-"}</span>
```
#### Correct
```tsx
<span>{formatMoney(detail.totalAmount)}</span>
```
## Convention: Frontend Design Token Sources
**What**: Runtime visual tokens use the sources that are actually consumed by
@@ -235,6 +281,7 @@ reference, not a copy of Feishu or Ant Design branding.
- Mobile reimbursement target: `/mobile/reimbursements/:reimbursementId`.
- PC reimbursement target: `/reimbursements/:reimbursementId`.
- Mobile source state: `<Link state={{ from: currentMobileSource }}>`.
- PC source state: `<Link state={{ from: currentPcSource }}>`; `currentPcSource` is the trusted internal `pathname + search` of the current reimbursement list or approval task.
### 3. Contracts
@@ -242,6 +289,8 @@ reference, not a copy of Feishu or Ant Design branding.
- When `WorkflowTaskDetail.reimbursement` exists, the task detail exposes an explicitly named `查看完整报销单` or `查看报销单` link near the reimbursement summary.
- Reuse the existing business detail route and its permissions. Do not duplicate reimbursement items, invoice or attachment viewers inside the approval page merely to add reachability.
- Mobile navigation passes the current approval task URL through the existing `state.from` convention so the business detail back button returns to that task. This is a one-level source contract, not a new nested return stack.
- PC reimbursement list and task-detail exits use the same one-level `state.from` contract. The detail page accepts only `/reimbursements` list URLs and `/workflow/tasks/:id`; absolute/protocol-relative URLs, hashes, other modules and the current page are rejected. A direct open or refresh safely falls back to `/reimbursements`.
- PC detail return is a visible text action immediately before the detail hero, uses `replace`, and is not duplicated inside the business primary-action group or `更多`.
- Approval actions remain on the approval task page. Opening the business detail does not approve, reject, return or mutate the task.
### 4. Validation & Error Matrix
@@ -260,7 +309,7 @@ reference, not a copy of Feishu or Ant Design branding.
### 6. Tests Required
- Mobile E2E: task detail -> reimbursement detail -> items/evidence visible -> back to the same task -> approval actions still visible.
- PC E2E: task detail link -> existing reimbursement detail route and overview visible.
- PC E2E: filtered reimbursement list -> task/detail -> visible source-aware return; the list path retains its query string, task detail returns to the same task, and direct-open detail falls back to the reimbursement list.
- Assert non-reimbursement task details do not receive a reimbursement target.
- Run `cd client && npm run typecheck && npm run build`.