验证OCR关闭后的报销闭环(07-27-yang-jun-reimbursement-readiness)
这个提交包含在:
@@ -15,6 +15,98 @@ duplicate detection or attachment behavior.
|
||||
submission, approval, payment batch, receipt attachment and AP export when the
|
||||
Trellis task scope touches those areas.
|
||||
|
||||
## Scenario: OCR Disabled Without Mock Data
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: the production OCR provider is unavailable or intentionally disabled
|
||||
while employees still need to upload PDF invoices and submit reimbursements.
|
||||
- Purpose: keep real invoices out of the mock provider and avoid interpreting a
|
||||
historical database default as evidence that Tencent OCR actually ran.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Runtime env: `OCR_PROVIDER=off`.
|
||||
- Upload: `POST /api/invoice-pool/upload` with a PDF multipart `file`.
|
||||
- Bind: `POST /api/invoice-pool/:id/bind` with `{ reimbursementId }`.
|
||||
- Database: `InvoicePool.ocr_provider` is a non-null `OcrProvider` with schema
|
||||
default `tencent`; `ocr_attempts`, `ocr_last_requested_at`,
|
||||
`ocr_completed_at` and extracted invoice fields carry the execution evidence.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- `off` means no OCR queue entry, no provider request and no invented invoice
|
||||
code, number, date, buyer/seller or amount. A new PDF enters `available` and
|
||||
may be bound to an existing reimbursement item.
|
||||
- `mock` is restricted to local or explicitly isolated synthetic tests. It must
|
||||
not process a real production invoice.
|
||||
- Because `ocr_provider` is non-null with a database default, an off-mode record
|
||||
may still store `tencent` when the create payload omits that field. Consumers
|
||||
must not treat that value alone as proof of OCR execution.
|
||||
- Until real OCR or mandatory manual metadata entry is separately approved,
|
||||
finance checks the PDF original manually. SHA-256 duplicate detection remains
|
||||
active; invoice-code/number duplicate detection is unavailable for blank
|
||||
metadata.
|
||||
- A config-only provider switch keeps `BUILD_SHA` unchanged. Recreate every
|
||||
process that actually loads OCR config: server only when `JOBS_ENABLED=true`
|
||||
and no worker exists; server plus worker when an independent worker already
|
||||
exists. Never create a second job executor merely to satisfy a generic restart
|
||||
instruction.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- `OCR_PROVIDER=mock` in real operation -> block real invoice use and switch to
|
||||
`off` or a verified live provider.
|
||||
- `status != available` immediately after an off-mode upload -> stop and inspect
|
||||
the effective runtime config; do not patch invoice fields to bypass it.
|
||||
- Any extracted invoice business field, OCR attempt, request timestamp or
|
||||
completion timestamp appears in off mode -> fail the acceptance run and
|
||||
investigate provider/job topology.
|
||||
- `ocr_provider=tencent` with attempts `0`, null request/completion timestamps
|
||||
and null extracted fields -> historical schema-default finding, not evidence
|
||||
of a Tencent request.
|
||||
- Blank metadata cannot bind or submit -> keep the test draft, clean it through
|
||||
normal APIs, then plan a code change; do not add an emergency production
|
||||
database edit.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: off-mode PDF is `available`, all business fields are null, attempts are
|
||||
`0`, it binds to a 0.01 synthetic reimbursement, submits, is returned, then
|
||||
the reimbursement is deleted and the invoice archived.
|
||||
- Base: live Tencent OCR is configured and verified with an authorized sample;
|
||||
normal extracted-field and duplicate rules apply.
|
||||
- Bad: leave production on mock, or decide that OCR ran merely because
|
||||
`ocr_provider` contains the schema default `tencent`.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Assert upload status is `available` under `OCR_PROVIDER=off`.
|
||||
- Assert invoice code, number, date, buyer, seller and all amount fields are
|
||||
null; assert attempts are `0` and request/completion timestamps are null.
|
||||
- Bind to a reimbursement with one valid item, submit through the real workflow,
|
||||
return instead of approve, delete the draft and archive the released invoice.
|
||||
- Final read-only checks: test accounts archived, active sessions `0`, test
|
||||
reimbursements `0`, pending tasks `0`, non-archived test invoices `0`, and
|
||||
Enterprise WeChat writeback remains skipped.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```js
|
||||
const ocrRan = invoice.ocr_provider === "tencent";
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```js
|
||||
const ocrRan = invoice.ocr_attempts > 0
|
||||
|| invoice.ocr_last_requested_at != null
|
||||
|| invoice.ocr_completed_at != null
|
||||
|| invoice.ocr_result_id != null;
|
||||
```
|
||||
|
||||
## Scenario: Payment Batch Receipt Attention Count
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
在新工单中引用
屏蔽一个用户