From 4cf73ce0d2908ea28cc14aeb62f9b96173b67449 Mon Sep 17 00:00:00 2001 From: shunli Date: Sat, 1 Aug 2026 11:05:31 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=8A=A5=E9=94=80=E4=BA=8B?= =?UTF-8?q?=E5=AE=9E=E4=BF=AE=E6=AD=A3=E5=AE=A1=E8=AE=A1=E8=A7=84=E7=BA=A6?= =?UTF-8?q?=EF=BC=8807-27-yang-jun-reimbursement-readiness=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spec/hyb-oa/reimbursement-invoice-ocr.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) 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