完善报销通知与付款进度(08-01-reimbursement-notification-progress-clarity)
这个提交包含在:
@@ -138,9 +138,12 @@ if (!hasPermission(actor, "payment.batch.review")
|
||||
workflow stages use the actual pending assignee when available. Future stages
|
||||
keep `actorName=null`; never guess a person from today's role holders.
|
||||
- A reimbursement approved without a batch makes `payment_batch_review` the
|
||||
current stage with `detail="待财务创建付款批次"`. A draft batch says
|
||||
`所在付款批次待非制单人复核`; shared-batch wording must not imply that this
|
||||
reimbursement alone is being reviewed again.
|
||||
current stage with `detail="待财务安排付款"`. A draft batch says
|
||||
`待非制单人复核付款`; employee-facing wording does not expose the internal
|
||||
batch object or imply that this reimbursement alone is being reviewed again.
|
||||
- The final stage label is `付款完成`, and its pending/current/failed detail
|
||||
explicitly says it is a finance registration step rather than an approval.
|
||||
Internal finance pages may continue to use the precise `付款批次` term.
|
||||
- Missing workflow, batch or receipt facts degrade only their stages. Earlier
|
||||
known stages remain visible; missing downstream objects are normal, not 5xx.
|
||||
- `targetHref` is returned only when the viewer already has one of the existing
|
||||
@@ -163,7 +166,7 @@ if (!hasPermission(actor, "payment.batch.review")
|
||||
- Batch `draft|reviewed|executed|failed|cancelled` -> map to explicit current,
|
||||
completed or exception states without changing the batch.
|
||||
- Executed payment + zero active receipts -> completed payment with
|
||||
`付款已完成,回单待留存`; positive count -> `付款与回单已留存`.
|
||||
`付款已完成,回单待留存`; positive count -> `付款和回单已留存`.
|
||||
- Viewer lacks batch-read permission -> `targetHref=null`; do not return 403 for
|
||||
the reimbursement detail itself.
|
||||
- Database/query failure -> existing 5xx behavior and server logging; do not
|
||||
@@ -171,7 +174,7 @@ if (!hasPermission(actor, "payment.batch.review")
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: `财务复核=通过` followed by current `待财务创建付款批次`, so the owner
|
||||
- Good: `财务复核=通过` followed by current `待财务安排付款`, so the owner
|
||||
can diagnose the exact responsibility without asking three people.
|
||||
- Base: a newly submitted reimbursement shows known submission, every real
|
||||
configured approval task, and the two future payment stages with no guessed
|
||||
@@ -210,3 +213,106 @@ const approvalStages = workflowTasks.map((task) => buildApprovalStage(task));
|
||||
approvalStages.push(...buildFutureSnapshotStages(workflowSnapshot, workflowTasks));
|
||||
return [submittedStage, ...approvalStages, batchReviewStage, paymentStage];
|
||||
```
|
||||
|
||||
## Scenario: Payment Responsibility Notifications
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: creating a reimbursement payment batch, completing its maker-checker
|
||||
review, or extending notifications to another payment responsibility handoff.
|
||||
- These are action-required notifications, not new workflow approval tasks and
|
||||
not public business-domain events.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Notification types: `payment_batch.review_requested` and
|
||||
`payment_batch.execution_requested`.
|
||||
- Both use `targetType=payment_batch`, `targetId=<batch id>` and
|
||||
`targetPath=/payment-batches?id=<batch id>`.
|
||||
- Both resolve to the existing `reimbursement` IM domain while payment batches
|
||||
contain only reimbursement entities.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Batch creation resolves active employees whose roles include `manager|ceo`
|
||||
and whose effective role permissions include `payment.batch.review`; exclude
|
||||
the maker and never hard-code a person's name or employee ID.
|
||||
- Review completion first targets the active maker when that employee still has
|
||||
`payment.batch.manage`. If unavailable, fall back to other active
|
||||
`finance|finance_mgr` employees with that permission; always exclude the
|
||||
reviewer.
|
||||
- Create station notification records inside the same transaction as the batch
|
||||
creation/review state change. Dispatch through the shared IM dispatcher only
|
||||
after commit. A dispatch failure is logged and, once a channel attempt starts,
|
||||
retained as delivery evidence; it never rolls back the committed payment
|
||||
action.
|
||||
- Payment execution keeps the existing `reimbursement.paid` result notification
|
||||
to the owner/submitter. `付款完成` remains a finance action and must not be
|
||||
represented as another approval.
|
||||
- PC and mobile approval histories use the shared
|
||||
`client/src/lib/approvalDisplay.ts#workflowActionLabel`; `start|submit` both
|
||||
render as `提交`, and pages must not keep local label maps.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- No eligible non-maker reviewer -> create the batch with zero review
|
||||
notifications; do not weaken maker-checker or notify the maker.
|
||||
- Maker inactive or missing `payment.batch.manage` after review -> notify the
|
||||
eligible finance fallback; no eligible fallback -> review remains committed
|
||||
with zero execution notifications.
|
||||
- Maker attempts review or a reviewed batch is reviewed again -> existing
|
||||
deterministic `409 VALID_001`; zero responsibility notifications and zero IM
|
||||
dispatch.
|
||||
- IM disabled, recipient push disabled/unbound, or no matching channel -> station
|
||||
notification remains; dispatcher returns a fixed skipped reason without
|
||||
rolling back the payment action.
|
||||
- Notification record creation fails inside the transaction -> the protected
|
||||
payment state change fails atomically; no partial handoff is reported.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: finance creates one weekly batch, the eligible non-maker reviewer gets
|
||||
one notification, then successful review returns the action to the active
|
||||
maker for payment and receipt registration.
|
||||
- Base: no eligible reviewer is configured; the draft batch remains visible for
|
||||
diagnosis and no unqualified management broadcast is fabricated.
|
||||
- Bad: notify every finance/manager account, let the maker review their own
|
||||
batch, send before transaction commit, or label payment completion as an
|
||||
approval.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Route tests assert qualified reviewer resolution, maker exclusion, active
|
||||
maker preference, inactive-maker finance fallback and reviewer exclusion.
|
||||
- Negative tests assert maker conflict, repeated review and duplicate batch
|
||||
creation produce no notification/dispatch.
|
||||
- Dispatcher tests assert both types are IM-supported and resolve to the
|
||||
reimbursement domain; dispatch failure must leave the committed batch state.
|
||||
- PC/mobile E2E asserts `start` renders `提交` from the shared label map and the
|
||||
five-stage employee projection ends with `付款复核` / `付款完成(非审批)`.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```js
|
||||
notifyEmployeesByRole(["manager", "finance"]);
|
||||
await tx.paymentBatch.update({ data: { status: "reviewed" } });
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```js
|
||||
const recipients = await resolvePaymentExecutionRecipientIds(tx, {
|
||||
makerId: batch.created_by,
|
||||
reviewerId: actor.id,
|
||||
});
|
||||
const notifications = await createNotificationsWithRecords(tx, {
|
||||
recipientEmployeeIds: recipients,
|
||||
type: NOTIFICATION_TYPES.paymentBatchExecutionRequested,
|
||||
targetType: "payment_batch",
|
||||
targetId: batch.id,
|
||||
});
|
||||
// after commit
|
||||
await flushNotificationDispatchTasks({ tasks: [buildDispatchTask(notifications)] });
|
||||
```
|
||||
|
||||
在新工单中引用
屏蔽一个用户