fix: 财务本人报销由总经理独立审批(09-04-reimbursement-state-gap-finance-view)
这个提交包含在:
@@ -17,6 +17,96 @@ task before modifying approval behavior.
|
||||
- Reviewer review is required after changes touching workflow, status machine,
|
||||
approval rules, permissions or migrations.
|
||||
|
||||
## Verification: Direct Manager And CEO Are The Same Employee
|
||||
|
||||
`scripts/qa/test-invoice-first-http.cjs` supports opt-in
|
||||
`RUN_MANAGER_IS_CEO_TEST=1` with the existing disposable-database guard.
|
||||
It creates employee, ceo+employee and finance+employee synthetic identities;
|
||||
employee and finance report to the CEO, with no manager-role holder.
|
||||
The default separate-manager fixture remains supported.
|
||||
|
||||
For the fixture's manager → ceo → finance rule, the direct-manager task is
|
||||
assigned by employee relationship without requiring a manager role. The same
|
||||
CEO must approve two separate steps; they are not automatically collapsed by
|
||||
employee identity. Verify pending task identity/role, HTTP approval and replay
|
||||
rejection at each step, then approved status and zero pending tasks.
|
||||
Payment creation by finance and review by CEO remain separate responsibilities.
|
||||
|
||||
This is a contract test, not permission to deduplicate workflow steps or to add
|
||||
manager roles to production. It does not cover finance submitting its own
|
||||
reimbursement, CEO self-fallback, or every amount/workflow definition. Keep
|
||||
those scenarios separate from the employee-owned fixture and do not infer
|
||||
production account readiness from synthetic login results.
|
||||
|
||||
## Scenario: Finance-Owned Reimbursement Uses One CEO Approval
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
New reimbursement submissions and resubmissions whose owner holds explicit
|
||||
`finance` use one CEO approval. A finance submitter acting for an ordinary
|
||||
employee does not trigger this rule. This is not global same-person step
|
||||
deduplication and does not rewrite existing instances.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
`buildApprovalWorkflowSnapshot({ tx, approvalRule, ownerId, submitterId, entityType, logger })`
|
||||
applies the specialization only to `entityType: "reimbursement"`.
|
||||
`buildFinanceOwnerApprovalSnapshot({ tx, ownerId, submitterId })` is shared
|
||||
with reimbursement operational precheck and returns null for non-finance owners.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
The new step is `approval_1_ceo_finance_owner`, titled 总经理审批, with existing
|
||||
`{ type: "role", value: "ceo" }` assignment semantics. Require at least one
|
||||
active, unarchived CEO outside both owner and submitter before submission.
|
||||
Another finance employee does not change this rule; finance+CEO applicants
|
||||
still cannot self-approve. Execution-time actor/principal self-approval and
|
||||
business-role guards remain authoritative. No role or payment permission changes.
|
||||
Finance creates the payment batch; the separate eligible non-maker reviews it.
|
||||
For this new step, transfer targets and both execution actor and delegation
|
||||
principal must still hold active, unarchived CEO membership. A transferred
|
||||
specific-user assignment is not a substitute for that role. This restriction
|
||||
is enforced in shared action handling, not only frontend choices.
|
||||
Reimbursement list pending-workflow actions and workflow task actions also
|
||||
exclude non-CEO actors for this step, including transferred assignees whose
|
||||
role was revoked and non-CEO delegates. The server guard remains authoritative.
|
||||
|
||||
### 4. Validation / Error Matrix
|
||||
|
||||
No non-applicant CEO: snapshot raises 409 `no_actionable_approver` with
|
||||
`finance_owner_ceo_unavailable`; operational precheck exposes a blocking reason
|
||||
with that code and administrator guidance. This operational check runs with
|
||||
budget precheck both enabled and disabled. Submission must leave no pending
|
||||
instance on rejection; role deactivation after submission is not prevented by
|
||||
an earlier precheck.
|
||||
Ineligible transfer/execution returns 403 with reason code
|
||||
`reimbursement.finance_owner_ceo_required`. Old executors can parse the new
|
||||
step but lack this guard: rolling back only the image after new snapshots exist
|
||||
is not a safe way to continue processing them.
|
||||
|
||||
### 5. Good / Base / Bad Cases
|
||||
|
||||
Good: finance submits → CEO approves once → finance makes batch → CEO reviews.
|
||||
Base: employee-owned manager/CEO/finance steps and CEO-owned finance fallback
|
||||
retain their earlier semantics. Bad: grant finance to CEO, allow self-approval,
|
||||
or treat reimbursement approval as payment authorization.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
Workflow unit and operational-precheck tests cover identity exclusions and
|
||||
unchanged non-reimbursement paths. The disposable HTTP fixture with
|
||||
`RUN_MANAGER_IS_CEO_TEST=1 RUN_OWNER_APPROVAL_TEST=1` covers the new chain,
|
||||
return/resubmit, second finance, multi-role self-denial, unavailable CEO with
|
||||
budget gates on/off, and separate payment review. Browser-enabled runs cover
|
||||
desktop/mobile submission/approval and payment detail handoff.
|
||||
This is synthetic validation, not production login, live OCR or bank acceptance.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
Wrong: keep three identical CEO approvals or wait for a finance self-task to fail.
|
||||
Correct: explicitly construct one CEO step for finance-owned new snapshots;
|
||||
retain a separate payment review and immutable historical workflow facts.
|
||||
|
||||
## Scenario: System Operator Is Not A Business Approver
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
在新工单中引用
屏蔽一个用户