补齐报销待组批财务入口 [09-04-reimbursement-state-gap-finance-view][R1]
这个提交包含在:
@@ -625,6 +625,7 @@ const NOTIFICATION_TYPE_TO_DOMAIN_SCOPE = Object.freeze({
|
||||
- Builder: `buildPaymentProcessingSemantics({ status, createdAt, reviewedAt, executedAt, cancelledAt, cancelledReason, requiresBankReceipt, receiptCount, paymentOrderExportCount })`.
|
||||
- Channel helpers: `resolveItemPaymentChannel(batch, item)` and `requiresBankReceipt(batch)`.
|
||||
- Payment detail: `GET /api/payment-batches/:id` returns `processingProgress.stages[]`; list responses do not return this field.
|
||||
- Pending batching queue: `GET /api/payment-batches/candidates?q=&page=&pageSize=` is the only eligibility source; `/payment-batches?view=pending&q=<business-no>` is its refreshable UI entry.
|
||||
- Stage states: `completed | current | pending | exception | terminated`.
|
||||
|
||||
### 3. Contracts
|
||||
@@ -637,6 +638,8 @@ const NOTIFICATION_TYPE_TO_DOMAIN_SCOPE = Object.freeze({
|
||||
- `failed` uses `exception`. A cancellation before review makes the review stage `exception` and payment stage `terminated`; a cancellation after review completes review and makes payment `exception`. `terminated` is a neutral stopped state, not pending, completed or failed.
|
||||
- `actorSource=creator|reviewer|executor|canceller` is an internal semantic reference. The shared service never queries employees; each response assembler resolves only a nullable actor name and never exposes actor IDs inside progress.
|
||||
- When an authorized reimbursement has entered a batch, its processing progress exposes at most one batch link. `draft` and cancellation before review attach it to `付款复核`; `reviewed|executed|failed` and cancellation after review attach it to `付款完成`. Unauthorized actors receive neither link.
|
||||
- An `approved` reimbursement without a batch is current at the reimbursement-only label `组建付款批次`; `付款复核` remains pending until a `draft` batch exists. The payment-batch detail keeps the shared `批次创建` label, so the employee projection does not rename the shared definition globally.
|
||||
- Payment managers default to the URL-backed `待组批` view and may create from the existing candidate contract. Review-only actors default to `付款批次` and must neither render nor request candidates. Candidate eligibility is never reimplemented in React.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
@@ -647,10 +650,12 @@ const NOTIFICATION_TYPE_TO_DOMAIN_SCOPE = Object.freeze({
|
||||
- `reviewed + requiresBankReceipt=true + receiptCount>0` -> guide registration completion.
|
||||
- `cancelled + reviewedAt=null` -> review `exception`, payment `terminated`; `cancelled + reviewedAt!=null` -> review `completed`, payment `exception`.
|
||||
- Missing actor row or timestamp -> corresponding name/time is null; the detail response still succeeds.
|
||||
- `approved + no batch` -> `组建付款批次=current/待组批`, `付款复核=pending`; a created `draft` batch -> creation completed and review current.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: both pages import the same semantic builder, while the reimbursement route selects two payment stages and the payment route returns all three.
|
||||
- Good: both pages import the same semantic builder, while the reimbursement route returns the three payment stages and locally labels creation as `组建付款批次`.
|
||||
- Good: a payment manager follows a reimbursement deep link into one candidate list whose count, search and pagination all come from the server response and URL.
|
||||
- Base: a cash batch without a receipt shows `等待财务登记付款完成` and exposes the permitted registration action.
|
||||
- Bad: a route adds `if (batch.status === "reviewed")` to rewrite stage state/detail, or a client infers receipt requirements from `receiptCount===0`.
|
||||
- Bad: a cancelled batch shows a later `待处理` node, or a cash batch tells finance to upload a bank receipt.
|
||||
@@ -661,6 +666,7 @@ const NOTIFICATION_TYPE_TO_DOMAIN_SCOPE = Object.freeze({
|
||||
- A structural test extracts both route assembler functions and rejects route-local payment status matrices or duplicate `付款复核` / `付款完成` label definitions.
|
||||
- Route tests assert actor name/time projection, list/detail field separation and absence of snapshot, bank ciphertext and actor IDs inside progress.
|
||||
- Reimbursement route tests assert the five batch statuses, both cancellation positions and the no-permission case never produce duplicate batch links.
|
||||
- Browser tests assert manager/reviewer default URLs, review-only candidate request count `0`, one candidate DOM, server `total`, URL search/pagination, responsive overflow and create-to-detail navigation.
|
||||
- PC/mobile E2E uses synthetic responses for all five states, verifies `terminated`, bank/non-bank copy, one visible primary action per viewport and upload-to-register in-place switching.
|
||||
- Reimbursement E2E asserts the four-block order and that submit, print, remind, withdraw and delete retain their state/permission, reason/confirmation, request and refresh contracts after action-panel migration.
|
||||
|
||||
@@ -672,6 +678,9 @@ const NOTIFICATION_TYPE_TO_DOMAIN_SCOPE = Object.freeze({
|
||||
if (batch.status === "reviewed" && receiptCount === 0) {
|
||||
paymentStage.detail = "请上传银行回单";
|
||||
}
|
||||
|
||||
// Wrong: infer candidates again in the client.
|
||||
const pending = reimbursements.filter((item) => item.status === "approved" && !item.batchId);
|
||||
```
|
||||
|
||||
#### Correct
|
||||
@@ -683,4 +692,7 @@ const semantics = buildPaymentProcessingSemantics({
|
||||
receiptCount: batch._count?.receipt_attachments ?? 0,
|
||||
paymentOrderExportCount: batch.payment_order_export_count ?? 0,
|
||||
});
|
||||
|
||||
// Correct: the UI renders only the server candidate contract.
|
||||
const candidates = await fetchPaymentBatchCandidates({ q, page, pageSize });
|
||||
```
|
||||
|
||||
在新工单中引用
屏蔽一个用户