fix: 增加回单核对与付款登记门禁(09-08-reimbursement-payment-closeout)
这个提交包含在:
@@ -18,6 +18,107 @@ QA script patterns over new abstractions.
|
||||
|
||||
## Common Checks
|
||||
|
||||
### Receipt OCR Matching Service Boundary
|
||||
|
||||
- Failed evidence persistence is not OCR failure. In a separate bounded transaction,
|
||||
lock the batch then conditionally update the attachment by version/SHA/processing
|
||||
and active governance predicates. Persist only allowlisted system_failed,
|
||||
key_failed or snapshot_failed states; never an exception message or OCR payload.
|
||||
A failed fallback write returns failure_recording_failed, not a claimed durable
|
||||
failure. Do not automatically retry cloud calls or overwrite a newer job/void.
|
||||
- System failure states do not permit ordinary OCR retry or manual proposals.
|
||||
Administrator recovery requires fixing the prerequisite and a separately
|
||||
authorized version-bound requeue; no bulk reset or silent cloud retransmission.
|
||||
|
||||
- Bank reimbursement execute calls claimVerifiedReceipts inside the existing
|
||||
payment transaction after batch/source locks. Receipt existence alone never
|
||||
permits registration. Existing non-bank and travel-history paths are not
|
||||
silently converted into the reimbursement BankSlipOCR contract.
|
||||
- Claims reference the exact approved manual proposal. A unique transaction-alias
|
||||
table reserves both original and corrected identities against one claim.
|
||||
Original-reference reuse, concurrent manual/automatic claims and changed current
|
||||
manual-reviewer roles/participants fail closed; aliases and payment roll back
|
||||
together. Keep the migration's existing-claim alias backfill.
|
||||
- GET receipt-readiness uses RepeatableRead READ ONLY with the explicit readOnly
|
||||
service mode: no FOR UPDATE/SHARE, advisory locks or inserts. It is a UI hint;
|
||||
execution always repeats the locked validation. Never pass readOnly from user
|
||||
input into payment execution.
|
||||
|
||||
- Manual persistence uses a separate proposal tied to original verification, SHA
|
||||
and incremented attachment version. Save and review audit share the transaction;
|
||||
only id/status/version leave mutation endpoints. Association-ready means
|
||||
unchanged item association, not independent approval; manual approval does
|
||||
not itself record payment. Rejected proposals remain immutable history and
|
||||
manual rejection permits explicit OCR retry.
|
||||
- Manual endpoints preserve finance management or manager/ceo review permission,
|
||||
source-department and feature gates. Persistence locks batch, claim advisory,
|
||||
then attachment, rechecks original and corrected transaction claims, and rejects
|
||||
stale version/source or self-review. Approval revalidates matching; execute must
|
||||
still revalidate and claim in the actual payment transaction.
|
||||
|
||||
- Manual proposal preparation in paymentReceiptManualReview is not authorization:
|
||||
corrections or unavailable OCR require independent review; unchanged exact-match
|
||||
item association does not. Preserve original evidence, changed-field provenance,
|
||||
and original transaction fingerprints for persistent duplicate checks.
|
||||
- Never rewrite original failed/pending transactions into successful evidence,
|
||||
including conflicting status aliases. Original cross-page duplicate identities
|
||||
remain blocked even when manual fields change the reference or payer account.
|
||||
Internal normalized accounts must not be serialized into API responses.
|
||||
- Independent exception review requires explicit review permission and manager/ceo
|
||||
business role, outside maker, proposer and every source owner/submitter. Missing
|
||||
identity fails closed. Pure helper validation does not replace route scope,
|
||||
transaction/version guards, audit or execute-time revalidation.
|
||||
|
||||
- OCR upload/retry eligibility requires reimbursement plus a pending/failed bank
|
||||
item. The worker rechecks under the batch lock before reading storage or calling
|
||||
cloud OCR; out-of-scope queued receipts return to legacy without transmission.
|
||||
Cash and travel attachment storage is not permission to send them to BankSlipOCR.
|
||||
- New eligible receipt uploads queue in the attachment/audit transaction; legacy attachments
|
||||
are not automatically sent to cloud OCR. cron and pg-boss share DB claiming;
|
||||
the dedicated `payment.receipt.ocr` queue disables automatic retries.
|
||||
- `POST /payment-batches/:id/receipt-attachments/:attachmentId/ocr-retry` requires
|
||||
finance management, feature/department and batch-state gates plus an integer
|
||||
`expectedVersion`. Version/state rejection uses
|
||||
`payment_batch.receipt_retry_unavailable`, not a generic unexplained failure.
|
||||
Requeue and audit commit together. Processing is eligible only after 15 minutes;
|
||||
queued/matched are not eligible. No amount/plaintext or OCR call in this route.
|
||||
- Attachment GET exposes `ocrState/ocrVersion/ocrStartedAt/ocrRetryAvailable`;
|
||||
the same eligibility function is used on retry. Eligibility is not permission:
|
||||
callers still need authorized editable batch context.
|
||||
|
||||
- Persistence: `storeReceiptRecognition(tx, config, { batchId, attachmentId,
|
||||
sourceSha256, expectedVersion, recognition })` locks batch then attachment,
|
||||
rejects voided/changed/superseded input, and writes encrypted allowlisted evidence.
|
||||
Future scheduling/correction must increment the attachment version under the same locks.
|
||||
- `claimVerifiedReceipts(tx, config, batch)` requires a locked current batch and
|
||||
the same transaction as payment recording. Revalidate latest evidence and frozen
|
||||
data, then claim unique transaction fingerprint and item. Never call in a separate
|
||||
committed transaction or consider it complete HTTP authorization by itself.
|
||||
- Claim lock namespace is `ADVISORY_LOCK_NAMESPACES.paymentReceiptClaims` (1002).
|
||||
With Prisma, project an integer from `pg_advisory_xact_lock`; selecting its void
|
||||
return directly can fail with P2010. Do not remove serialization to hide that failure.
|
||||
- Different fingerprint key IDs in historical claims block registration until an
|
||||
explicitly reviewed key migration; key rotation must not defeat deduplication.
|
||||
DB validation: `RUN_ISOLATED_DB_TEST=1 node scripts/qa/test-payment-receipt-db.cjs`.
|
||||
|
||||
- Internal services: `normalizeBankSlip(fields)`, `matchBankSlip(receipt, candidates)`
|
||||
in paymentReceiptMatching and `recognizePaymentReceipt({ buffer, mimeType, config })`
|
||||
in paymentReceiptOcr. These helpers are not themselves payment authorization.
|
||||
- BankSlipOCR keys vary by layout; support known aliases, reject conflicting or
|
||||
repeated semantic keys as ambiguous, never silently pick the first transaction.
|
||||
Currency/account/principal matching is per item, not against the batch total.
|
||||
- Normalize money with strict grouping/precision and integer minor units; preserve
|
||||
account leading zeroes. Masked accounts and non-final transaction states cannot
|
||||
establish a match. OCR matching never establishes bank authenticity.
|
||||
- Process all PDF pages, retain expected/processed counts, and never accept a
|
||||
partially failed response as a complete receipt. Provider errors are sanitized;
|
||||
no raw response or public receipt URL. Normalized accounts remain internal
|
||||
sensitive data, not API/log payloads.
|
||||
- Unit contracts: paymentReceiptMatching.test.js and paymentReceiptOcr.test.js.
|
||||
Their passing does not prove persistent duplicate prevention, independent
|
||||
manual review, source-state revalidation, or execute transaction safety; those
|
||||
gates require separate database/HTTP acceptance before wiring payment writes.
|
||||
|
||||
- `cd server && node --check src/routes/reimbursements.js`
|
||||
- `cd server && node --check src/routes/workflow.js`
|
||||
- `cd server && ./scripts/regression-workflow-actions.sh`
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# Frontend React / Vite / Design
|
||||
|
||||
## Receipt Verification Dialog
|
||||
|
||||
- Reuse the receipt card and existing Dialog/Input/Button patterns. No separate
|
||||
receipt center. Show original and proposed summaries; account fields remain masked.
|
||||
Original-file preview keeps its existing authorized path.
|
||||
- Unchanged association submits page/item only. Corrections submit changed fields
|
||||
only; never round-trip account masks as bank accounts. Unedited fields are merged
|
||||
against the server's version-bound original evidence, not a client snapshot.
|
||||
- Detail query keys include actor/batch/attachment and forward AbortSignal. Disable
|
||||
background form refetch; explicit refresh clears selections, corrections and reason.
|
||||
Identity or batch switches close the dialog, and unmounted mutation callbacks must
|
||||
not refresh another identity's screen.
|
||||
- Backend canReview is an eligibility projection, not authorization. Hide review
|
||||
actions when false; actual review still checks role, permission and all participants.
|
||||
Save/review never call payment execution or upload.
|
||||
|
||||
## Source Of Truth
|
||||
|
||||
Frontend visual and interaction design uses the installed `frontend-design`
|
||||
|
||||
在新工单中引用
屏蔽一个用户