补齐审批退回结果通知 [09-04-approval-return-notification-gap]

这个提交包含在:
2026-09-04 16:22:27 +08:00
父节点 a6ffb7fc3c
当前提交 2e69fe4edb
共修改 19 个文件,包含 857 行新增和 14 行删除
+62
查看文件
@@ -550,6 +550,68 @@ reimbursement UI renders only declared capabilities
legacy reject endpoint and rejected history remain compatible but undiscoverable from the new UI
```
## Scenario: Return Outcome Notifications
### 1. Scope / Trigger
- Trigger: adding a workflow entity that supports return, or changing return outcome notifications, recipients, copy, deep links or IM routing.
- These are runtime notification projections of a completed return, not new public business-domain events or another workflow state transition.
### 2. Signatures
- Runtime types: `reimbursement.returned`, `leave.returned`, `purchase_request.returned` and `travel_request.returned`.
- Domain scopes: reimbursement -> `reimbursement`, leave -> `leave`, purchase request -> `procurement`, travel request -> `travel`.
- Attendance correction has no return action and therefore no returned notification type.
### 3. Contracts
- Every returned type must be registered together in `NOTIFICATION_TYPES`, `IM_SUPPORTED_NOTIFICATION_TYPES` and `NOTIFICATION_TYPE_TO_DOMAIN_SCOPE`. A type missing from any one registry is incomplete even if station notification creation still works.
- The workflow outcome mapping is the only entity-to-returned-type switch. Station notifications, email tasks and IM dispatch tasks consume that same projection; do not add channel-local entity branches.
- Return copy says the request was returned and can be modified and resubmitted. It must not reuse rejected wording while the business object is restored to `draft`.
- Reuse each entity's existing applicant/submitter resolution and deep link. Duplicate employee IDs produce one station notification and one recipient entry.
- Business state, workflow task/history and leave/budget release remain authoritative. Notification construction or dispatch failure is logged without sensitive provider payloads and never rolls back a completed return.
- Production channel routing is external configuration. Shipping support for a new type does not authorize adding it to a production channel allowlist; that write requires a separate approval and audited management-API update.
### 4. Validation & Error Matrix
- Missing explicit `allowedTypes` value or domain scope in a configured whitelist -> channel does not match; empty arrays retain the existing meaning of no restriction for that dimension.
- Station notification creation throws after the return state/history writes -> return still succeeds and commits; no email or IM work is fabricated from the failed projection.
- IM provider or Outbox dispatch fails after commit -> business return remains committed and delivery/outbox evidence follows the existing dispatcher contract.
- No natural production return sample -> code and routing readiness may pass, but real enterprise-WeChat delivery stays blocked rather than being inferred from synthetic tests.
### 5. Good/Base/Bad Cases
- Good: a returned purchase request creates one `purchase_request.returned` station notification and projects the same copy and target into email/IM work; the applicant sees that the draft can be modified and resubmitted.
- Base: production routing does not yet allow the new type, so the station notification exists while IM records a deterministic routing skip; the completed return remains unchanged.
- Bad: register a returned type only in `NOTIFICATION_TYPES`, write a channel-specific entity switch, reuse rejected copy, or let a provider failure change the workflow response.
### 6. Tests Required
- Table-driven registry test covering all three registries for the four supported returned types and proving the IM-supported registry has no duplicates.
- Outcome matrix for reimbursement, leave, purchase, travel and attendance correction.
- Side-effect tests for station notification, email task, IM dispatch task, recipient deduplication, copy, target identity and deep link for leave/purchase/travel.
- Route-level failure test proving notification creation failure does not undo task completion, business `draft` state or return history.
- IM routing allow/reject coverage and management API schema coverage for all newly supported values.
### 7. Wrong vs Correct
#### Wrong
```js
const NOTIFICATION_TYPES = Object.freeze({ leaveReturned: "leave.returned" });
// Missing IM support/domain registration; station works but IM silently skips it.
```
#### Correct
```js
const NOTIFICATION_TYPES = Object.freeze({ leaveReturned: "leave.returned" });
const IM_SUPPORTED_NOTIFICATION_TYPES = Object.freeze([NOTIFICATION_TYPES.leaveReturned]);
const NOTIFICATION_TYPE_TO_DOMAIN_SCOPE = Object.freeze({
[NOTIFICATION_TYPES.leaveReturned]: NOTIFICATION_DOMAIN_SCOPES.leave,
});
```
## Scenario: Shared Payment Processing Projection
### 1. Scope / Trigger