10 KiB
10 KiB
B2 提交错误可解释性与 OCR 降级合同核验
Goal
修复 B2 票据明细门禁“只返回参数校验失败、员工不知道改哪里”的缺陷,并恢复既有 OCR 降级合同:有真实 OCR 时保持金额硬校验;未实际执行 OCR 的 PDF 允许随报销提交,由财务明确执行人工票面核对。
Background
- 生产公网构建为
0f3ecfabb7c8,B2 已全局开启。2026-08-30 通过受控生产只读门禁确认唯一 server 运行容器为OCR_PROVIDER=tencent;未读取其他环境变量,因此不触发 B2 flag 回退。 - 0014 的员工截图发生在重新整理票据期间,当时页面只显示“参数校验失败”。后续生产只读复现确认:该单现有 5 条需票明细、5 张 bound 发票,每条明细恰好关联 1 张金额有效发票;未归属数、缺有效票明细数和超票面明细数均为 0。发票在截图后完成重新关联,单据已于当晚成功提交并进入
reviewing。 - 0014 的历史失败响应无法事后精确恢复:
AppError.details只返回浏览器,服务端日志不记录该结构,失败事务也不留数据库快照。现有证据只能确认失败发生在“票据重新关联尚未完成”阶段,不能再断言当时一定是unlinkedInvoiceCount或missingInvoiceItemIds。 server/src/routes/reimbursements.js的 B2 提交门禁返回details.items=invoice_item_guard_failed,并携带unlinkedInvoiceCount、missingInvoiceItemIds和amountExceededItems。client/src/api/errorMessages.ts没有映射invoice_item_guard_failed;PC 与移动详情页又把统一actionError放在概况区或操作区,因此员工既看不到具体原因,也容易把错误归因到不相关字段。- 已绑定但
linked_item_id=null的发票并非完全不可见:PC 和移动材料区都会显示“明细:未关联”。真正缺陷是状态不突出、提交错误没有定位到该票据,且页面没有直接重新指定明细的动作;该发票也不会出现在“可添加发票”列表中,员工只能先移除再重新添加。 .trellis/spec/hyb-oa/reimbursement-invoice-ocr.md与docs/报销发票附件规则.md规定:OCR_PROVIDER=off时 PDF 不执行 OCR、不生成金额,但仍可绑定明细并提交,由财务查看原件人工核对。当前 B2 以total_amount != null定义“有效发票”,与该合同冲突;腾讯模式下个别 OCR 金额缺失也会被错误描述成“缺票”。- 明细发生日期是实际费用发生或消费日期,不等同于发票日期。任何后续从票生成明细的设计只能把发票日期作为待确认建议值。
Scope
本 task 覆盖 R1-R4:冻结生产诊断证据、修复 B2 结构化错误与 PC/移动就近反馈、为未归属 bound 发票提供原子关联动作、恢复未执行 OCR 票据的人工核对降级合同。R5 所列详情页整体信息结构和自动生成明细能力明确延期。
Requirements
R1 生产证据与应急边界
- 保留生产只读结论:运行 provider 为
tencent,不回退 B2 flag。 - 记录 0014 当前三类 B2 检查均通过、已成功提交,以及历史失败分支不可恢复的原因;不得要求员工再开浏览器开发者工具补证据。
- 若后续受控检查发现正式环境实际运行
off,仍须先单独取得生产写入授权才可回退 flag;本 task 的规划或实施批准不自动授权生产 flag、账号、报销或票据写入。 - 正式环境若为
mock,停止真实票据使用并按既有禁用 mock 合同单独处置,不把 mock 结果作为票面证据。
R2 三类 B2 错误可解释性
- 为
invoice_item_guard_failed增加稳定中文反馈,分别覆盖:已绑定但未归属明细的发票、需票明细没有关联 PDF、明细金额高于可自动比较的关联票面合计。 - 将“已关联 PDF 但 OCR 未取得金额”从“缺少发票”中拆出,给出独立原因和恢复路径;不得让员工以为需要重复上传一张已经存在的票。
- 错误反馈使用明细序号、费用类型、说明摘要或页面内发票序号,不向普通用户展示数据库 UUID、API 路径、原始 details 或“发票池”等内部术语。
- PC 与移动端都提供提交失败摘要,并在对应明细或发票卡片附近显示原因和下一步;动态错误使用可访问的告警语义,toast 只能补充,不能是唯一反馈。
- 服务端仍是提交权威;前端预检查不得替代服务端结构化错误和并发后的最终反馈。
R3 未归属发票的可恢复操作
- 对
bound + linked_item_id=null的发票显示显著“未选择报销明细”错误态,并直接提供“选择明细”动作。 - 重新指定明细必须是服务端单事务操作:锁定发票和报销单、校验操作人、草稿/驳回状态、同一报销单内目标明细后,只更新
linked_item_id。不得在前端串联“移除 → 重新添加”,避免票据短暂回到可用池或被并发占用。 - 操作成功后刷新详情和提交检查;操作失败时错误留在对应票据卡片,不清空现有绑定关系。
- 本轮只处理未归属 bound 发票的恢复,不扩展为所有已关联发票的任意改绑工作台。
R4 OCR 金额证据与降级合同
- 将“是否有关联 PDF”和“是否取得可比较票面金额”分开判断。需票明细没有任何 bound PDF 时才属于缺票。
- 金额证据按实际记录判定,不只看数据库默认的
ocr_provider:total_amount非空:该张发票具有可自动比较金额;- PDF 未发生 OCR 请求/尝试且金额为空:视为 off/历史降级票据,进入财务人工票面核对;
- 已发生 OCR 请求/尝试但金额仍为空:视为金额识别失败,提交时单独阻断并提示重试识别或联系财务。
- 同一明细的所有关联发票都有金额时,使用
Prisma.Decimal求和并执行“申报金额不得高于票面合计”的硬阻断。 - 同一明细只要包含未执行 OCR 的降级票据,自动金额合计即不完整:允许提交,但必须在员工提交反馈和审批详情中明确标记“需财务人工核对票面”,不得宣称金额自动校验通过。
- “有金额 + 降级无金额”的混合场景同样进入人工核对,不用已知部分金额制造错误的超额结论;这不是绕过校验,而是把无法完成的自动判断显式移交财务。
- 不要求员工人工补录票面金额,不新增数据库迁移;既有金额、绑定关系和 flag off 回滚语义保持兼容。
R5 后续 UX 边界
- “从我的票据选择并生成/补全明细”、明细与票据合并展示、草稿页信息结构、处理进度和 PC sticky 操作栏属于后续“报销详情页信息结构与操作重排”任务,不扩入本 P0。
- 本 P0 只增加结构化错误、金额核对状态和未归属票据的最小恢复动作,不重写整个详情页。
- 后续从发票生成明细时,发票日期仅可作为待员工确认的建议值;实际发生日期必须允许修改并保留“不一定等于发票日期”的业务口径。
Acceptance Criteria
- 记录当前生产 server 的实际
OCR_PROVIDER=tencent,证据只包含 provider 值和观察时间,不包含其他 env。 - 记录 0014 当前 5 条需票明细、5 张金额有效 bound 发票、三类 B2 计数均为 0,且已成功提交进入
reviewing;明确历史失败只能定位到重新关联未完成阶段,不能伪造精确 details。 - 明确当前不触发 B2 flag 回退;未经独立授权不执行任何生产写入。
- 未归属、缺关联 PDF、金额识别失败、申报超票面四类问题在 PC 和移动端显示自然中文,并定位到对应明细/票据,不暴露内部 ID。
- 未归属 bound 发票可在票据卡片上原子选择本单明细,不需要先移除再重新添加。
- 未执行 OCR 且金额为空的关联 PDF 可随需票明细提交,并在提交反馈与审批详情明确标记财务人工核对;已尝试 OCR 但金额缺失仍被单独阻断。
- 所有关联票据金额完整时继续硬拦申报超额;混合“有金额 + 降级无金额”进入人工核对而非错误的部分金额比较。
- 两项免票补助、Tencent 正常金额路径、B2 flag off、既有关联关系和 owner/submitter 写权限保持兼容。
- 后端聚焦测试、前端错误解析与 PC/移动测试、typecheck/build、Prisma 校验及 Trellis/governance 门禁通过。
- 面向员工的改动只有部署和公网合成验收通过后才告知可测试;生产部署、flag 写入和真人数据修改分别取得明确授权。
Test Plan
- 后端聚焦测试覆盖未归属、无关联 PDF、金额识别失败、完整金额超额、off/未执行 OCR 人工核对、混合金额证据、免票补助、flag off 兼容和 0014 类正常通过。
- 原子关联接口覆盖成功、权限、状态、跨单、已有归属与并发零写入。
- 前端单元/组件及 PC/移动 E2E 覆盖安全中文映射、就近定位、原子恢复、人工核对提示、可访问告警和内部 ID 不泄露。
- 运行 Prisma validate、相关 Vitest、前端 typecheck/build、聚焦 Playwright、
node scripts/qa/check-ui-design-rules.mjs --strict、流程/文档/Trellis governance 与git diff --check;凡修改用户可见前端文案,strict UI 文案门禁必须在提交和部署前运行,不能只依赖公网 bundle 扫描兜底。部署后只用合成草稿做公网验收。
Out of Scope
- 报销详情页整体信息架构、个人票据入口、从票自动生成明细、处理进度重排、PC 主操作布局和财务一页式审批。
- OCR provider 采购、腾讯凭据轮换、人工票面金额录入、数据库 provider 枚举或其他 schema migration。
- 修改 0014/0016/0017/0018 的明细、发票、金额、状态或审批流程。
- 将未归属恢复动作扩展成通用的已关联发票批量改绑能力。
Confirmed Production Evidence
- 运行 provider:
tencent;不回退 B2。 - 0014 当前:
status=reviewing、5 条明细均需票、5 张 bound 发票、未归属 0、空金额 0、缺票明细 0、超票面明细 0。 - 时间线:明细先完成重建;截图发生在重新关联完成之前;5 张发票随后完成关联,单据成功提交并创建待审批 workflow。
- 历史失败的精确
error.details已不可恢复,因为该响应未写日志且失败事务无快照;后续不再向员工索要 F12 证据。