收敛报销明细与材料交互(07-27-yang-jun-reimbursement-readiness)
这个提交包含在:
@@ -576,6 +576,98 @@ reply.header(
|
||||
);
|
||||
```
|
||||
|
||||
## Scenario: Reimbursement Material Presentation And Item Linking
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: changing PC or mobile reimbursement-detail presentation for invoice
|
||||
originals, supporting attachments or their relationship to expense items.
|
||||
- Purpose: let finance reconcile each item with its invoice without exposing a
|
||||
separate technical “open file” button for every material.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Existing detail payload: `detail.items[]`, `detail.invoices[]` with
|
||||
`invoice.linkedItemId: string | null`, and `detail.attachments[]`.
|
||||
- Existing preview actions: `openInvoiceMutation` and
|
||||
`handleOpenAttachment`; WeCom continues through `previewFile` and PC through
|
||||
its existing browser-safe path.
|
||||
- Stable UI selectors: `reimbursement-materials` and
|
||||
`mobile-reimbursement-materials`; existing invoice/attachment child test IDs
|
||||
remain available for focused regressions.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Present invoice originals and supporting attachments under one user-facing
|
||||
`报销材料` section with separate type labels and counts. Do not merge their
|
||||
database types, API fields, authorization, audit or lifecycle rules.
|
||||
- Within each expense item, render only invoices whose
|
||||
`linkedItemId === item.id`. The linked invoice summary is itself a semantic
|
||||
preview button and shows an invoice identifier, seller and amount.
|
||||
- Keep every invoice, including unlinked or stale-linked invoices, in the
|
||||
material section. Never infer a link from amount, order or list position.
|
||||
- The material information row is the preview target; do not append a repeated
|
||||
visible `查看原件` button to each file. Draft-only add/remove/upload/delete
|
||||
actions remain separate buttons so preview cannot mutate data.
|
||||
- Do not introduce a file-browser route, carousel, bulk download or aggregate
|
||||
backend endpoint for the small-team detail page unless later real use proves
|
||||
that the inline material list is insufficient.
|
||||
- Clickable rows have a descriptive `aria-label`, visible focus, disabled state
|
||||
during open, a chevron affordance and a minimum 44px mobile touch target.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Matching `linkedItemId` -> show the invoice inside that item and in the
|
||||
material section.
|
||||
- `linkedItemId=null` or target item absent -> show only in the material
|
||||
section; do not attach it to the first or amount-matching item.
|
||||
- No invoices and no attachments -> one `暂无报销材料` empty state.
|
||||
- Preview pending -> disable repeated opens; existing preview errors and
|
||||
download-reason behavior remain authoritative.
|
||||
- Read-only approver -> preview rows visible, mutation actions absent.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: a hotel item shows its linked hotel invoice inline, while an unlinked
|
||||
payment screenshot remains under `报销材料` and both originals are previewable.
|
||||
- Base: one item, one linked invoice and no attachment renders one inline
|
||||
invoice summary plus one typed material row.
|
||||
- Bad: flatten invoices away from items, guess links by equal amount, merge all
|
||||
file records into one backend type, or add a `查看原件` button beside every
|
||||
row.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Focused PC and WeCom Playwright fixtures contain at least one linked and one
|
||||
unlinked invoice plus one supporting attachment.
|
||||
- Assert the linked invoice appears only in the matching item; the unlinked
|
||||
invoice appears in materials but not in any item.
|
||||
- Assert material counts, absence of exact visible `查看原件` buttons, PC open
|
||||
URL behavior, WeCom `previewFile` name/size/URL, 44px touch target and mobile
|
||||
portrait/landscape overflow.
|
||||
- Preserve editable add/remove/upload/delete regression coverage, then run
|
||||
frontend typecheck/build and Trellis governance gates.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```tsx
|
||||
detail.invoices.map((invoice) => <Button>查看原件</Button>);
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```tsx
|
||||
detail.invoices
|
||||
.filter((invoice) => invoice.linkedItemId === item.id)
|
||||
.map((invoice) => (
|
||||
<button aria-label={`查看关联发票:${formatInvoiceDisplayTitle(invoice)}`}>
|
||||
{formatInvoiceDisplayTitle(invoice)}
|
||||
</button>
|
||||
));
|
||||
```
|
||||
|
||||
## Scenario: Payment Batch Receipt Attention Count
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
在新工单中引用
屏蔽一个用户