feat(detail-page-structure-progress-actions): 统一详情处理进度与操作区

这个提交包含在:
2026-08-03 16:00:42 +08:00
父节点 8d428bd51d
当前提交 b7d5761067
共修改 31 个文件,包含 1453 行新增和 383 行删除
+71
查看文件
@@ -489,3 +489,74 @@ const label = status === "draft" ? "草稿" : status;
{["reviewed", "failed"].includes(batch.status) ? <PaymentRegistrationFields /> : null}
{needsBankReceipt ? <UploadReceipt /> : canRegister ? <RegisterPaid /> : null}
```
## Scenario: Shared Payment Processing Projection
### 1. Scope / Trigger
- Trigger: adding or changing payment-stage labels, status projection, receipt/order guidance, reimbursement progress or payment-batch detail progress.
- This is a read-only cross-layer projection. It does not add workflow tasks, alter the payment write state machine or relax receipt/maker-checker gates.
### 2. Signatures
- Shared service: `server/src/services/paymentProcessingProgress.js`.
- 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.
- Stage states: `completed | current | pending | exception | terminated`.
### 3. Contracts
- The shared service is the only runtime source for payment stage keys, labels and `draft|reviewed|executed|failed|cancelled` state projection. Reimbursement and payment-batch routes only assemble their own DTO view, resolve actor names and add permission-gated links.
- Reimbursement keeps its single-item `paymentBatchItem` view; payment detail keeps the whole-batch view. Do not replace these with one database-aware DTO builder, and do not add route-local status branches after calling the shared builder.
- `requiresBankReceipt` is shared with the execute guard and resolves the effective item channel, including the legacy null-channel reimbursement fallback to `bank`. Receipt count alone must never decide whether a receipt is required.
- A reviewed non-bank batch is current at `付款完成` with guidance to register completion. It must not mention uploading a bank receipt or generating a bank order.
- A reviewed bank batch with no active receipt distinguishes no order vs order generated; an active receipt changes guidance to registration. Payment-order count affects copy only when `requiresBankReceipt=true`.
- `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.
### 4. Validation & Error Matrix
- Unknown/missing status -> created remains completed and downstream stages remain pending; do not guess a current or completed business fact.
- `reviewed + requiresBankReceipt=false` -> current payment guidance is registration only, regardless of receipt/order counts.
- `reviewed + requiresBankReceipt=true + receiptCount=0 + exportCount=0` -> guide offline payment and receipt upload.
- Same with `exportCount>0` -> say the bank order was generated and wait for payment/receipt; do not imply money moved.
- `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.
### 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.
- 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.
### 6. Tests Required
- Pure table-driven unit tests cover all five statuses, both cancellation positions, bank/non-bank channels, order count, receipt count and actorSource.
- 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.
- 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.
### 7. Wrong vs Correct
#### Wrong
```js
if (batch.status === "reviewed" && receiptCount === 0) {
paymentStage.detail = "请上传银行回单";
}
```
#### Correct
```js
const semantics = buildPaymentProcessingSemantics({
status: batch.status,
requiresBankReceipt: requiresBankReceipt(batch),
receiptCount: batch._count?.receipt_attachments ?? 0,
paymentOrderExportCount: batch.payment_order_export_count ?? 0,
});
```