记录真实报销首跑风险(07-27-yang-jun-reimbursement-readiness)

这个提交包含在:
2026-07-27 21:23:15 +08:00
父节点 9694179c25
当前提交 4147a6960f
共修改 3 个文件,包含 27 行新增和 0 行删除
@@ -32,6 +32,8 @@ duplicate detection or attachment behavior.
- Database: `InvoicePool.ocr_provider` is a non-null `OcrProvider` with schema - Database: `InvoicePool.ocr_provider` is a non-null `OcrProvider` with schema
default `tencent`; `ocr_attempts`, `ocr_last_requested_at`, default `tencent`; `ocr_attempts`, `ocr_last_requested_at`,
`ocr_completed_at` and extracted invoice fields carry the execution evidence. `ocr_completed_at` and extracted invoice fields carry the execution evidence.
- Duplicate index: `@@unique([file_sha256, ocr_provider, ocr_type])` partitions
the SHA-256 namespace by provider and OCR type.
### 3. Contracts ### 3. Contracts
@@ -47,6 +49,13 @@ duplicate detection or attachment behavior.
finance checks the PDF original manually. SHA-256 duplicate detection remains finance checks the PDF original manually. SHA-256 duplicate detection remains
active; invoice-code/number duplicate detection is unavailable for blank active; invoice-code/number duplicate detection is unavailable for blank
metadata. metadata.
- A future nullable/off-provider migration must not change only the stored
provider value. Backfill historical rows to one canonical namespace or replace
the composite unique key with an intentional file-level uniqueness contract;
otherwise the same bytes can be uploaded once before and once after migration.
- Before changing provider semantics, count historical `mock` rows uploaded by
non-test employees. Any such rows need an explicit dedupe/review plan because
their provider namespace already differs from current default rows.
- A config-only provider switch keeps `BUILD_SHA` unchanged. Recreate every - A config-only provider switch keeps `BUILD_SHA` unchanged. Recreate every
process that actually loads OCR config: server only when `JOBS_ENABLED=true` process that actually loads OCR config: server only when `JOBS_ENABLED=true`
and no worker exists; server plus worker when an independent worker already and no worker exists; server plus worker when an independent worker already
@@ -65,6 +74,8 @@ duplicate detection or attachment behavior.
- `ocr_provider=tencent` with attempts `0`, null request/completion timestamps - `ocr_provider=tencent` with attempts `0`, null request/completion timestamps
and null extracted fields -> historical schema-default finding, not evidence and null extracted fields -> historical schema-default finding, not evidence
of a Tencent request. of a Tencent request.
- Provider enum/default or unique-key change without backfill/index redesign ->
block migration review because it splits the duplicate-detection namespace.
- Blank metadata cannot bind or submit -> keep the test draft, clean it through - 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 normal APIs, then plan a code change; do not add an emergency production
database edit. database edit.
@@ -84,6 +95,9 @@ duplicate detection or attachment behavior.
- Assert upload status is `available` under `OCR_PROVIDER=off`. - Assert upload status is `available` under `OCR_PROVIDER=off`.
- Assert invoice code, number, date, buyer, seller and all amount fields are - Assert invoice code, number, date, buyer, seller and all amount fields are
null; assert attempts are `0` and request/completion timestamps are null. null; assert attempts are `0` and request/completion timestamps are null.
- Migration tests for provider semantics must upload identical bytes on both
sides of the migration and assert that the second upload is still rejected or
reuses the canonical row.
- Bind to a reimbursement with one valid item, submit through the real workflow, - 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. return instead of approve, delete the draft and archive the released invoice.
- Final read-only checks: test accounts archived, active sessions `0`, test - Final read-only checks: test accounts archived, active sessions `0`, test
@@ -105,6 +105,7 @@
- 用户选择并批准关闭 mock。现场拓扑没有独立 worker,`JOBS_ENABLED=true`,因此只重建实际承载 Web 与后台任务的 server;不得为了“同时重启 worker”而凭空创建第二个作业执行器。 - 用户选择并批准关闭 mock。现场拓扑没有独立 worker,`JOBS_ENABLED=true`,因此只重建实际承载 Web 与后台任务的 server;不得为了“同时重启 worker”而凭空创建第二个作业执行器。
- `OCR_PROVIDER=off` 复验确认:新 PDF 直接进入 `available`,发票代码、号码、日期、购销方和金额字段均为空,OCR 尝试为 0、请求/完成时间为空;票据可以绑定到单明细 0.01 元测试报销并正常提交、退回和清理。 - `OCR_PROVIDER=off` 复验确认:新 PDF 直接进入 `available`,发票代码、号码、日期、购销方和金额字段均为空,OCR 尝试为 0、请求/完成时间为空;票据可以绑定到单明细 0.01 元测试报销并正常提交、退回和清理。
- `InvoicePool.ocr_provider` 是非空枚举且 schema 默认 `tencent`;off 路径传入 `undefined` 后数据库仍写入该默认值。该字段只是历史 schema 默认,不代表发生过 OCR 调用;当前 UI 不展示 provider。是否在后续迁移中改为 nullable 应与腾讯 OCR/人工录入产品决策一起规划,不阻断本次真实报销。 - `InvoicePool.ocr_provider` 是非空枚举且 schema 默认 `tencent`;off 路径传入 `undefined` 后数据库仍写入该默认值。该字段只是历史 schema 默认,不代表发生过 OCR 调用;当前 UI 不展示 provider。是否在后续迁移中改为 nullable 应与腾讯 OCR/人工录入产品决策一起规划,不阻断本次真实报销。
- `InvoicePool` 当前使用 `@@unique([file_sha256, ocr_provider, ocr_type])`。后续若把 provider 改成 nullable 或新增 `off` 枚举,必须同步回填历史行或重设计唯一键;只改新行 provider 会把同一文件拆进两个查重命名空间。生产只读核查显示历史 `mock` 票据共 1 张且仅由测试账号上传,非测试员工上传数为 0。
- off 模式下发票号为空,基于 `invoice_code + invoice_number` 的查重不可用,但文件 SHA-256 查重仍生效;当前小团队短期由财务查看 PDF 原件并人工核对,不在本任务增加强制补录。 - off 模式下发票号为空,基于 `invoice_code + invoice_number` 的查重不可用,但文件 SHA-256 查重仍生效;当前小团队短期由财务查看 PDF 原件并人工核对,不在本任务增加强制补录。
- 草稿删除成功后,详情页在导航完成前仍短暂请求已删除的详情与 precheck,浏览器产生预期 404 控制台噪声,但用户已回到空草稿列表。这是非阻断前端时序 finding,不影响数据清理结果。 - 草稿删除成功后,详情页在导航完成前仍短暂请求已删除的详情与 precheck,浏览器产生预期 404 控制台噪声,但用户已回到空草稿列表。这是非阻断前端时序 finding,不影响数据清理结果。
@@ -135,3 +136,11 @@
- 不创建法体、不补银行账号、不导入其余成员。 - 不创建法体、不补银行账号、不导入其余成员。
- 不把首次真实业务扩成管理员代办的生产自动化测试。 - 不把首次真实业务扩成管理员代办的生产自动化测试。
- 不创建新的企微测试成员,不给测试主管财务或管理员权限,不让测试单通过审批或进入付款链路。 - 不创建新的企微测试成员,不给测试主管财务或管理员权限,不让测试单通过审批或进入付款链路。
## 8. 真实首跑补充风险复核
- 生产三名真实参与人均 active 且企微绑定唯一,但尚无 `AUTH_WECOM_LOGIN_SUCCESS` 记录;企微客户端入口仍需本人首登验证。
- 当前 active 费用标准为 0、杨俊部门适用预算池为 0;真实金额不会触发费用标准或预算阻断。active 法体为 0、杨俊未绑定法体,但现有报销提交和付款批次候选不以法体为硬门禁。
- 金额审批规则不是同一条:`[0,500)` 为 `manager → finance`,`[500,5000)` 为 `manager → finance_mgr`,`[5000,+∞)` 为 `manager → ceo → finance_mgr`。
- 当前 active 角色持有人:`finance=1`、`ceo=1`、`finance_mgr=0`。因此低于 500 元可路由到马建勇后再到王芝玲;达到 500 元的单据会生成无人可处理的 `finance_mgr` 角色待办。修复应调整审批规则到既有最小角色,不给王芝玲扩为权限过宽的 `finance_mgr`。
- 最终审批只把报销置为 `approved`,不会自动创建付款批次或付款记录;付款批次仍由王芝玲主动创建并由马建勇复核。
@@ -64,6 +64,8 @@
## 6. 用户真实验收 ## 6. 用户真实验收
- [ ] 在杨俊提交前修正金额审批规则:`[500,5000)` 的末级由 `finance_mgr` 改为 `finance`,`[5000,+∞)` 保留 `ceo` 且末级由 `finance_mgr` 改为 `finance`;不得通过给王芝玲增加 `finance_mgr` 绕过。
- [ ] 规则写入前后只读核对三档金额边界(499.99 / 500 / 5000)及 active 角色持有人,确认每个角色节点都至少有一名实际处理人。
- [ ] 请杨俊本人从企业微信进入 OA,确认身份解析成功并能看到报销入口。 - [ ] 请杨俊本人从企业微信进入 OA,确认身份解析成功并能看到报销入口。
- [ ] 杨俊使用真实业务数据创建报销,至少添加一条费用明细并上传/绑定状态有效的真实票据后提交。 - [ ] 杨俊使用真实业务数据创建报销,至少添加一条费用明细并上传/绑定状态有效的真实票据后提交。
- [ ] 确认马建勇能看到该真实审批待办并按业务事实处理。 - [ ] 确认马建勇能看到该真实审批待办并按业务事实处理。
@@ -92,3 +94,5 @@
- finding:生产 OCR 为 mock 且真实腾讯 OCR 未配置,合成 PDF 自动生成 Mock 字段;草稿删除后页面导航前产生已删除详情/precheck 的 404 控制台噪声。前者阻断杨俊真实票据上传,后者不阻断业务。 - finding:生产 OCR 为 mock 且真实腾讯 OCR 未配置,合成 PDF 自动生成 Mock 字段;草稿删除后页面导航前产生已删除详情/precheck 的 404 控制台噪声。前者阻断杨俊真实票据上传,后者不阻断业务。
- OCR off 复验:配置切换后未改构建;三次有业务写入的复验/诊断均按“退回 → 删除 → 归档 → 停用”收口。累计 4 张合成测试票据(含第一轮)均已归档,未归档 0;测试报销与 pending 待办均为 0。 - OCR off 复验:配置切换后未改构建;三次有业务写入的复验/诊断均按“退回 → 删除 → 归档 → 停用”收口。累计 4 张合成测试票据(含第一轮)均已归档,未归档 0;测试报销与 pending 待办均为 0。
- OCR off 结论:空发票业务字段可以绑定并提交,主管退回成功;真实使用暂由财务查看 PDF 原件并人工核对。文件 SHA-256 查重仍有效,发票号查重暂不可用。 - OCR off 结论:空发票业务字段可以绑定并提交,主管退回成功;真实使用暂由财务查看 PDF 原件并人工核对。文件 SHA-256 查重仍有效,发票号查重暂不可用。
- 真实首跑复核:三名参与人 active 且企微绑定唯一,但企微首登均未发生;费用标准 0、适用预算池 0、active 法体 0,法体不阻断当前报销。历史 mock 票据仅 1 张且为测试账号上传,非测试员工为 0。
- 审批规则 finding:`[0,500)` 可由马建勇和唯一 finance 王芝玲处理;`[500,5000)` 与 `[5000,+∞)` 均以 `finance_mgr` 收尾,但 active `finance_mgr=0`,达到 500 元会形成无人处理待办。未修正规则前不得笼统通知杨俊可提交。