diff --git a/.trellis/spec/hyb-oa/reimbursement-invoice-ocr.md b/.trellis/spec/hyb-oa/reimbursement-invoice-ocr.md index 1fae21b9..8ebea450 100644 --- a/.trellis/spec/hyb-oa/reimbursement-invoice-ocr.md +++ b/.trellis/spec/hyb-oa/reimbursement-invoice-ocr.md @@ -691,6 +691,9 @@ await previewFileInWecom({ url: bundle.signedUrl, name: bundle.originalName, siz - `PATCH /api/reimbursements/:id` accepts nullable camel-case counterparts. - Detail response includes `travelSummary: { isTravel, purpose, location, startDate, endDate }`. +- A privileged correction of an already submitted reimbursement writes an + `AuditLog` in the same transaction. Its `extra.changes` is an object keyed by + changed field name, with `{ before, after }` scalar snapshots. ### 3. Contracts @@ -702,6 +705,17 @@ await previewFileInWecom({ url: bundle.signedUrl, name: bundle.originalName, siz min/max travel-item dates; location remains `未填写` and is never guessed. - Submit requires all four fields only for travel reimbursements; ordinary reimbursements remain unaffected. +- An administrator correcting an already submitted amount, expense date, + travel-summary date or other core declared financial fact must record the + exact normalized `before` and `after` values in the restricted database audit + entry. Dates use `YYYY-MM-DD`; decimal amounts use canonical strings. The + audit also records a sanitized reason and operation/task reference. +- The repository-visible Trellis task, docs and generated evidence record only + changed field names, invariant results and counts. They must not copy the + real purpose, location, amount, ticket content or audit payload. +- Do not put attachment bytes, OCR/provider payloads or unrelated personal data + into `extra.changes`. Existing `system.audit.view` authorization remains the + access boundary for the database audit. ### 4. Validation & Error Matrix @@ -710,18 +724,33 @@ await previewFileInWecom({ url: bundle.signedUrl, name: bundle.originalName, siz - Travel submit with missing fields -> `400` with `travelSummary: incomplete` and stable `missingFields`. - Edit while not draft/rejected -> existing `409 invalid_edit_status`. +- Privileged correction changes a core declared fact but lacks a field-level + `before` or `after` snapshot -> abort the transaction; a field-name-only + audit is insufficient. +- A historical correction is later found without its original snapshot -> do + not invent or reconstruct an audit value from memory. Record the evidence + gap and enforce the complete audit contract on future corrections. ### 5. Good/Base/Bad Cases - Good: travel item plus complete purpose/location/date range submits normally. - Base: office-supply reimbursement has no travel summary and is not blocked. +- Good: an authorized date correction atomically updates the row and writes + `extra.changes.expenseDate = { before, after }` using synthetic or real + database values visible only to authorized auditors. - Bad: infer a destination from invoice OCR, attachment name or free text. +- Bad: overwrite a submitted expense date and audit only + `changedFields: ["expenseDate"]`, leaving the original fact unrecoverable. ### 6. Tests Required - Prisma validation and all migrations against an empty PostgreSQL database. - Route tests for detail fallback, complete/incomplete travel submit, date order and non-travel compatibility; frontend typecheck/build and PC/mobile UI tests. +- Privileged-correction tests use synthetic data and assert one transaction, + exact normalized `before/after`, sanitized reason, unchanged amount/material/ + workflow invariants, rollback on audit failure and no real values in generated + repository evidence. ### 7. Wrong vs Correct @@ -731,12 +760,26 @@ await previewFileInWecom({ url: bundle.signedUrl, name: bundle.originalName, siz travelLocation = invoice.ocrResult.sellerName; ``` +```js +extra = { changedFields: ["expenseDate"] }; // original declared fact is lost +``` + #### Correct ```js travelLocation = reimbursement.travel_location ?? null; ``` +```js +extra = { + changes: { + expenseDate: { before: "2026-01-02", after: "2026-01-03" }, + }, + reason: "authorized_correction", + operationRef: "TASK-SYNTHETIC", +}; +``` + ## Scenario: Payment Batch Receipt Attention Count ### 1. Scope / Trigger