实现企业银行账户与建行制单(corporate-bank-account-payment-order)
这个提交包含在:
@@ -18,6 +18,107 @@
|
||||
redacted form.
|
||||
- `scripts/qa/check-trellis-governance.mjs` must pass before commit.
|
||||
|
||||
## Scenario: Bank Account Snapshots And Payment-Order Export
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: company payment-account maintenance, employee payee-bank maintenance,
|
||||
payment-batch creation, full-account reveal or bank payment-order export.
|
||||
- OA prepares payment evidence and a bank-import file; it does not call a bank
|
||||
payment API or treat the generated file as proof of payment.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- DB: `BankInstitution`, `PaymentAccount`, `Employee.bank_institution_id`,
|
||||
`PaymentBatch.payment_account_snapshot_json`, item `snapshot_json.payee`, and
|
||||
`payment_order_export_*` fields.
|
||||
- Reveal APIs use `POST` with JSON `{ "reason": "2-500 chars" }`; ordinary read
|
||||
APIs return only bank name and masked account.
|
||||
- Payment-order API: `POST /api/payment-batches/:id/payment-order` with a reason;
|
||||
success is an in-memory BIFF8 `.xls` download with `Cache-Control: no-store`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Employee account editing remains HR/admin-owned. Never grant
|
||||
`employee.bank.manage` to finance, finance manager or system administrator.
|
||||
- Company account editing is finance-manager/admin-owned; ordinary finance is
|
||||
masked read plus reason-audited full reveal.
|
||||
- A new bank batch freezes the exact existing ciphertext for payer and payees.
|
||||
Copy ciphertext directly; decrypt only to calculate the masked suffix. Never
|
||||
decrypt and re-encrypt while building a snapshot.
|
||||
- Review UI reads masked values from the frozen snapshot. Finance manual-payment
|
||||
reveal also reads the frozen item snapshot, not the employee's current bank
|
||||
profile. An employee profile change after review must not change the payment
|
||||
target for that batch.
|
||||
- Ordinary batch, AP and export DTOs must explicitly select safe fields and must
|
||||
never return raw snapshot JSON, `v1:` ciphertext or a full account.
|
||||
- Payment-order preflight returns a safe blocker array containing fixed codes
|
||||
and, when useful, the reimbursement business number. Collect every batch and
|
||||
item blocker before returning; do not restore fail-fast "fix one, find one"
|
||||
behavior. The UI maps those fixed codes to natural Chinese and keeps manual
|
||||
payment visible for legacy or unsupported batches.
|
||||
- Legacy batches without ciphertext snapshots may still complete through manual
|
||||
payment plus receipt, but they can neither export a payment order nor reveal a
|
||||
supposed frozen account through a live-profile fallback.
|
||||
- A payment-order file is an efficiency artifact. A bank receipt remains the
|
||||
hard evidence required before registering bank payment complete.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Missing/mixed legal entity, no unique active payer, missing bank institution,
|
||||
missing account or decrypt failure -> reject batch creation with `409` and no
|
||||
batch/item writes.
|
||||
- First release bank code is not `CCB` for any payer/payee -> reject the whole
|
||||
export; do not emit `02`, skip a row or create a partial file.
|
||||
- Batch not `reviewed`, legacy snapshot or malformed snapshot -> reject export
|
||||
with `409`; do not increment export count or send file bytes.
|
||||
- Reveal without permission or approved finance business role -> `403`.
|
||||
- Reveal/export without a 2-500 character reason -> `400 VALID_001` and no full
|
||||
account/file response.
|
||||
- Successful export audit/update failure -> no download response.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: review shows a masked frozen CCB account; finance supplies a reason and
|
||||
temporarily reveals that same frozen account for manual transfer.
|
||||
- Base: a historical batch displays compatibility metadata, proceeds by manual
|
||||
payment and receipt, and clearly has no payment-order capability.
|
||||
- Bad: export or manual-transfer reveal re-queries the employee profile after
|
||||
review, silently uses a changed account, or returns raw snapshot JSON.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Permission matrix for employee/company account manage, masked read and full
|
||||
reveal; include an explicit system-admin denial.
|
||||
- Privacy-negative assertions over batch/list/detail/AP responses for raw
|
||||
accounts, `v1:` and raw snapshots.
|
||||
- Batch creation tests for single legal entity, direct ciphertext copy,
|
||||
CCB-only, decrypt failure and legacy behavior.
|
||||
- BIFF8 generator tests plus independent `xlrd==2.0.2` readback for three sheets,
|
||||
15 headers, seven string columns, leading-zero `01`, long accounts, Chinese,
|
||||
amount formatting and deterministic row order.
|
||||
- Build one synthetic batch with at least two independent payer/payee blockers;
|
||||
assert the DTO/error contains both safe blocker entries and emits no file,
|
||||
export counter or success audit.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```js
|
||||
const liveEmployee = await prisma.employee.findUnique({ where: { id: ownerId } });
|
||||
return decryptStringOrThrow(config, liveEmployee.bank_account_encrypted);
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```js
|
||||
const encrypted = paymentBatchItem.snapshot_json?.payee?.bankAccountEncrypted;
|
||||
assertReviewedFinanceReveal({ batch, actor, reason, encrypted });
|
||||
await writeRevealAudit();
|
||||
return decryptStringOrThrow(config, encrypted);
|
||||
```
|
||||
|
||||
## Scenario: Defensive Security Finding Summaries
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
在新工单中引用
屏蔽一个用户