修复报销进度审批节点覆盖(07-27-yang-jun-reimbursement-readiness)

这个提交包含在:
2026-08-01 13:38:05 +08:00
父节点 07ac5415c6
当前提交 f33b3569c6
共修改 7 个文件,包含 386 行新增和 45 行删除
+51 -22
查看文件
@@ -109,13 +109,31 @@ if (!hasPermission(actor, "payment.batch.review")
- Response: `processingProgress.stages[]` with `key`, `label`, `state`,
nullable `actorName`, `occurredAt`, `result`, `comment`, `detail` and
permission-gated `targetHref`.
- Stable stage keys: `submitted`, `manager_approval`, `finance_review`,
`payment_batch_review`, `payment_and_receipt`.
- Fixed stage keys: `submitted`, `payment_batch_review` and
`payment_and_receipt`. Each persisted workflow task contributes one unique
approval-stage key derived from task identity; clients treat keys as opaque
strings and never infer responsibility from their value.
### 3. Contracts
- Always return the five stages in the stable order above. UI labels describe
responsibilities and never expose step IDs, role codes or enum values.
- Always return submission first, then one stage per real workflow task in the
query's stable `created_at,id` order. Because the engine creates only the
current task, append not-yet-materialized future steps from the immutable
`workflow_steps_snapshot` after the current step, then return payment-batch
review and payment/receipt. The current two-approval production rule therefore
returns five stages from initial submission onward; configured extra or
repeated-role approvals increase the array length instead of being discarded,
merged or temporarily hidden.
- Never collect workflow tasks into a `Map` keyed by semantic responsibility:
two finance, manager or CEO tasks are distinct historical facts and later
tasks must not overwrite earlier ones. The UI renders `stages` with a plain
ordered `map` and contains no fixed-length or positional responsibility logic.
- Approval labels use a readable non-generic `step_name` first. Empty,
ordinal (`步骤2`, `审批节点1`, `step 3`), raw technical identifier or generic
(`审批`, `审核`, `复核`) names fall back through role mapping
(`manager -> 主管审批`, `finance|finance_mgr -> 财务复核`,
`ceo -> 负责人复核`) and finally `审批`. UI labels never expose step IDs,
role codes or sequence numbers.
- Completed stages use the actual completer/actor and completion time. Current
workflow stages use the actual pending assignee when available. Future stages
keep `actorName=null`; never guess a person from today's role holders.
@@ -131,8 +149,15 @@ if (!hasPermission(actor, "payment.batch.review")
### 4. Validation & Error Matrix
- No workflow instance -> submission/applicant state remains readable; approval
and payment stages stay pending.
- No workflow instance -> submission/applicant state remains readable and the
approval segment falls back to pending `主管审批` / `财务复核` placeholders.
- Workflow instance with no task yet -> build pending approval stages from its
immutable snapshot. Current task exists but later tasks do not -> append those
future snapshot stages with `actorName=null`; never infer future employees.
- A third unknown task -> one additional approval stage in its real position;
it must not disappear before the payment stages.
- Two tasks with the same role -> two unique ordered stages with their own
actor, state, time and comment; neither may overwrite the other.
- Workflow approved + no batch -> payment-batch stage is current, future actor
remains null.
- Batch `draft|reviewed|executed|failed|cancelled` -> map to explicit current,
@@ -148,36 +173,40 @@ if (!hasPermission(actor, "payment.batch.review")
- Good: `财务复核=通过` followed by current `待财务创建付款批次`, so the owner
can diagnose the exact responsibility without asking three people.
- Base: a newly submitted reimbursement shows known submission/current approval
and three future stages with no future names.
- Bad: display an expected checker before batch creation, label a shared batch
as `您的报销待复核`, or hide the whole timeline because no receipt exists.
- Base: a newly submitted reimbursement shows known submission, every real
configured approval task, and the two future payment stages with no guessed
future names.
- Bad: classify tasks into manager/finance keys and overwrite duplicates, skip
the third task, display an expected checker before batch creation, label a
shared batch as `您的报销待复核`, or hide the whole timeline because no receipt
exists.
### 6. Tests Required
- Route tests for no workflow, pending/completed/returned/rejected tasks, all
five batch statuses, receipt count zero/positive and paid-without-batch legacy
data.
- Route tests for no workflow, empty-task instance, a current task plus future
snapshot-only steps, pending/completed/returned/rejected tasks, a third
unclassified task, two same-role tasks, all five batch statuses, receipt count
zero/positive and paid-without-batch legacy data.
- Assert future stages have null actors and ordinary applicants receive no
batch link or batch-adjacent business data.
- PC/mobile tests assert five responsibility labels, no `第 N 步`, comments on
exception nodes, four-block order and no loss of earlier stages when a
downstream source is absent.
- PC/mobile tests assert variable-length ordered rendering, no `第 N 步` or raw
ordinal label, comments on exception nodes, four-block order and no loss of
earlier stages when a downstream source is absent.
### 7. Wrong vs Correct
#### Wrong
```js
return workflowInstance ? buildTimeline(workflowInstance) : null;
for (const task of workflowTasks) {
stageByRole.set(classify(task), buildStage(task));
}
```
#### Correct
```js
return buildProcessingProgress({
reimbursement,
workflowInstance: workflowInstance ?? null,
paymentBatchItem: paymentBatchItem ?? null,
});
const approvalStages = workflowTasks.map((task) => buildApprovalStage(task));
approvalStages.push(...buildFutureSnapshotStages(workflowSnapshot, workflowTasks));
return [submittedStage, ...approvalStages, batchReviewStage, paymentStage];
```