fix: 隔离门户待办身份并取消旧请求(09-11-reimbursement-payment-handoffs)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-09-11 20:21:46 +08:00
父节点 9e41f107fa
当前提交 5d8cdf12b4
共修改 10 个文件,包含 466 行新增和 26 行删除
@@ -23,6 +23,16 @@ and verification contracts. Do not copy broad aesthetic guidance from
- Reimbursement desktop payment links use content width (`justify-self-start`) and the existing 44px button height, not grid stretching across the aside. The creation label is `创建批次`; it navigates to the existing pending view and never creates a batch itself. Keep mobile touch sizing and the generic action-panel layout unchanged. Verify actual desktop bounds and that navigation sends no batch-creation POST.
- When `PaymentBatchesPage` retains its `lg:hidden` mobile action bar, its desktop action card must keep `hidden lg:block` as well as `lg:static`; changing stickiness must not remove responsive visibility. The adjacent progress stays visible. Cover the narrow PC route in addition to the dedicated mobile route, including receipt-upload action switching.
## Convention: Portal Query Eligibility Is Not Finance Display Eligibility
- `MobilePortalPage` loads authenticated `GET /portal/home` independently of `canUseFinance`. That flag gates the finance card, not `businessHandoffs`; the server still filters each item by role, permissions, participation, department and maker-checker.
- Test employee and CEO separately on PC and mobile, starting in a fresh browser context directly at the destination. Assert the mobile request actually occurs, the authorized link appears, processing removes it after reread, and failures show retry rather than an empty list.
- A finance mobile mock plus an employee desktop test is not a role-by-device matrix. Real isolated authentication/HTTP/browser checks must exercise employee returned work and CEO independent review without granting either a finance role.
- Portal response caches are employee-scoped: PC uses `["portal-home", me?.id]`, mobile uses `["mobile", "portal", "home", me?.id]`, and both queries require an existing identity. Keep these prefixes so existing invalidation callers still match; do not use previous-key placeholder data. `staleTime: 0` causes a refetch but does not isolate identities, and clearing PWA Cache Storage does not clear the singleton React Query cache.
- Identity keys also require cancellation: both portal query functions consume their `signal` and pass it through `fetchPortalHome(signal?: AbortSignal)` to `apiRequestData`. Otherwise an old request's late 401 can refresh under the new session, return the new employee's payload and cache it under the old employee's key. Validate actual transport cancellation plus A→B→A with the final A response held, not only A→B or successful late responses. Same-identity 401 refresh must still succeed; do not disable authentication refresh globally to make the test pass.
- Validate account changes within one browser context and document, using the UI logout/login flow rather than clearing caches, reloading or creating a new context. After the second identity is visible, old handoffs must remain absent while its response is held, after an error, through retry and after the old identity's delayed request completes. Assert that a request was actually held so a fast empty result cannot hide stale rendering; cover PC and mobile. Synthetic visibility events must reach the window listener (`bubbles: true` when dispatched on document). Real-auth fixtures must resolve profile fields from the authenticated ID; `request.employee` is not the full `/me` profile.
- A scoped portal fix does not prove every application query or same-account permission change has correct cache lifecycle. Keep unrelated authentication/cache work separate, and preserve the distinction between synthetic API browser checks, real isolated authentication/HTTP and production validation.
## Scenario: Actionable Validation And Selectable Payment Channels
### 1. Scope / Trigger