修复报销进度审批节点覆盖(07-27-yang-jun-reimbursement-readiness)
这个提交包含在:
@@ -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];
|
||||
```
|
||||
|
||||
在新工单中引用
屏蔽一个用户