实现企业银行账户与建行制单(corporate-bank-account-payment-order)

这个提交包含在:
2026-08-02 23:36:40 +08:00
父节点 f946d64539
当前提交 5261d2849b
共修改 46 个文件,包含 2751 行新增和 73 行删除
+101
查看文件
@@ -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