验证OCR关闭后的报销闭环(07-27-yang-jun-reimbursement-readiness)

这个提交包含在:
2026-07-27 20:54:19 +08:00
父节点 68600ec2d5
当前提交 9694179c25
共修改 7 个文件,包含 206 行新增和 14 行删除
@@ -15,6 +15,98 @@ duplicate detection or attachment behavior.
submission, approval, payment batch, receipt attachment and AP export when the submission, approval, payment batch, receipt attachment and AP export when the
Trellis task scope touches those areas. 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 ## Scenario: Payment Batch Receipt Attention Count
### 1. Scope / Trigger ### 1. Scope / Trigger
@@ -2,7 +2,7 @@
## 1. 设计目标 ## 1. 设计目标
以最小生产变更打通“杨俊企微登录 → 杨俊提交真实报销 → 马建勇审批 → 王芝玲财务处理”。本任务不新增产品能力,复用现有通讯录导入、用户主管写入、报销工作流和异机备份能力。 以最小生产变更打通“杨俊企微登录 → 杨俊提交真实报销 → 马建勇审批 → 王芝玲财务处理”,并在真实员工使用前由两个既有企微测试成员完成一次端到端体验验收。本任务不新增产品能力,复用现有通讯录导入、用户/角色管理、报销工作流和异机备份能力。
## 2. 已确认边界 ## 2. 已确认边界
@@ -12,6 +12,7 @@
- active 法体为 0,但当前报销创建、提交和费用控制不以法体为硬门禁,本任务不创建法体。 - active 法体为 0,但当前报销创建、提交和费用控制不以法体为硬门禁,本任务不创建法体。
- 杨俊的真实直属主管由用户确认是马建勇。 - 杨俊的真实直属主管由用户确认是马建勇。
- 自动备份 daily/watchdog/drill-reminder 与 WARCH3 service 的 `BUILD_SHA` 均已漂移,必须先修复。 - 自动备份 daily/watchdog/drill-reminder 与 WARCH3 service 的 `BUILD_SHA` 均已漂移,必须先修复。
- 通讯录中已有两个状态为已激活的专用企微测试成员,分别命名为“测试员工”和“测试主管”;生产 OA 尚未导入它们,因此不存在可直接登录的测试 OA 账号。
## 3. 数据流与执行门禁 ## 3. 数据流与执行门禁
@@ -27,6 +28,11 @@
→ 验证 OA 员工 active、企微绑定唯一、其他成员未导入 → 验证 OA 员工 active、企微绑定唯一、其他成员未导入
→ 通过现有用户管理接口设置 direct_manager_id=马建勇 → 通过现有用户管理接口设置 direct_manager_id=马建勇
→ 验证主管 active、不同人、具备 expense.approve,并核对审计 → 验证主管 active、不同人、具备 expense.approve,并核对审计
→ 从同一通讯录仅筛选两个既有测试成员并 dry-run/apply
→ 赋予最小角色并设置“测试员工 → 测试主管”的 OA 主管关系
→ 两个测试成员通过企微分别登录
→ 创建一条受控测试报销并由测试主管退回
→ 删除测试报销、归档测试票据、停用测试 OA 账号并撤销会话
→ 杨俊本人企微登录并提交真实报销 → 杨俊本人企微登录并提交真实报销
→ 马建勇审批,随后进入王芝玲财务处理链路 → 马建勇审批,随后进入王芝玲财务处理链路
``` ```
@@ -64,6 +70,44 @@
- 随后手工触发 daily 自动备份路径,验证源端 service 成功、ARM 批次完成且 dead-man/新鲜度检查通过。 - 随后手工触发 daily 自动备份路径,验证源端 service 成功、ARM 批次完成且 dead-man/新鲜度检查通过。
- 新鲜异机恢复点是人员数据写入的前置条件,但不会用于回滚单个人员变更。 - 新鲜异机恢复点是人员数据写入的前置条件,但不会用于回滚单个人员变更。
### 4.5 两个既有测试成员
- 不创建新的 OA-only 测试身份,也不调用企微成员创建接口;复用管理员导出表中的两个专用企微测试成员,通过通讯录导入建立 OA 员工与企微身份绑定。
- 导入输入只含“测试员工”和“测试主管”,手机号、邮箱、企微 userid 等原始字段不进入任务证据、聊天或日志。
- “测试员工”角色集合严格为 `employee`;“测试主管”角色集合严格为 `employee + manager`。后者只用于验证 `expense.approve` 和主管待办,不授予财务、薪资、银行资料或系统管理权限。
- 通过现有主管接口把测试员工的 `direct_manager_id` 指向测试主管;所有 OA 主管与角色写入均保留现有审计,不回写企微。
- 两个账号必须分别从企微入口登录,以同时验证通讯录导入后的身份绑定和实际页面权限;无需生成或传递临时密码。
### 4.6 受控测试报销生命周期
```text
测试员工创建报销
→ 使用 0.01 元最小测试值和“系统测试、非真实业务、禁止付款”描述
→ 上传不含真实票据/个人资料的安全 PDF
→ OCR 成功则继续;OCR 失败则在票据池人工填入唯一测试字段并确认可用
→ 绑定测试票据并提交
→ 测试主管确认待办可见并执行“退回”
→ 报销恢复 draft
→ 测试员工删除 draft(票据释放为 available)
→ 测试员工归档已释放票据
→ 管理员停用两个测试 OA 账号并核对会话撤销
```
- 测试主管不得点击“通过”;这保证测试单不会进入真实财务列表或付款批次。
- 删除仅允许发生在退回后的 `draft`;`POST /api/workflow/tasks/:taskId/return` 会在同一事务中把报销明确改回 `draft` 并终止 workflow instance,因此无需再执行申请人撤回。现有草稿删除接口会把已绑定票据释放为 `available`。
- 票据仅在解绑且为 `available`/`ocr_failed` 时手工归档;归档后保留最小审计痕迹但不再是可用业务票据。
- 账号收口使用现有 OA 停用接口,递增 token version 并撤销活动会话;企微侧两个测试成员保持原状。
### 4.7 受控测试发现与真实使用门禁
- 测试实际确认:生产当前使用 `OCR_PROVIDER=mock`,未配置腾讯 OCR 凭证;上传的合成 PDF 被写入明显的 Mock 发票号码、销售方、日期和金额并标记为可用。
- mock 只能作为合成测试依赖,不能让真实员工把其结果误认为真实识别。杨俊真实使用前必须二选一并再次复验:配置腾讯 OCR;或关闭 mock,明确采用人工录入/核对流程。
- 用户选择并批准关闭 mock。现场拓扑没有独立 worker,`JOBS_ENABLED=true`,因此只重建实际承载 Web 与后台任务的 server;不得为了“同时重启 worker”而凭空创建第二个作业执行器。
- `OCR_PROVIDER=off` 复验确认:新 PDF 直接进入 `available`,发票代码、号码、日期、购销方和金额字段均为空,OCR 尝试为 0、请求/完成时间为空;票据可以绑定到单明细 0.01 元测试报销并正常提交、退回和清理。
- `InvoicePool.ocr_provider` 是非空枚举且 schema 默认 `tencent`;off 路径传入 `undefined` 后数据库仍写入该默认值。该字段只是历史 schema 默认,不代表发生过 OCR 调用;当前 UI 不展示 provider。是否在后续迁移中改为 nullable 应与腾讯 OCR/人工录入产品决策一起规划,不阻断本次真实报销。
- off 模式下发票号为空,基于 `invoice_code + invoice_number` 的查重不可用,但文件 SHA-256 查重仍生效;当前小团队短期由财务查看 PDF 原件并人工核对,不在本任务增加强制补录。
- 草稿删除成功后,详情页在导航完成前仍短暂请求已删除的详情与 precheck,浏览器产生预期 404 控制台噪声,但用户已回到空草稿列表。这是非阻断前端时序 finding,不影响数据清理结果。
## 5. 失败与回退 ## 5. 失败与回退
- timer 重装或异机备份失败:不写杨俊数据,保留本地诊断并走既有脱敏群告警。 - timer 重装或异机备份失败:不写杨俊数据,保留本地诊断并走既有脱敏群告警。
@@ -72,6 +116,11 @@
- apply 成功但主管写入失败:保留已创建的 OA 员工和企微绑定,不删除、不恢复整库;主管仍为空时不能提交报销,可安全重试同一主管接口。 - apply 成功但主管写入失败:保留已创建的 OA 员工和企微绑定,不删除、不恢复整库;主管仍为空时不能提交报销,可安全重试同一主管接口。
- 主管写错:通过同一接口恢复写入前的 `direct_manager_id`,不恢复整库。 - 主管写错:通过同一接口恢复写入前的 `direct_manager_id`,不恢复整库。
- 真实报销提交失败:保留草稿和真实票据状态,按返回的业务错误定位;不得通过扩权、代批或伪造数据绕过。 - 真实报销提交失败:保留草稿和真实票据状态,按返回的业务错误定位;不得通过扩权、代批或伪造数据绕过。
- 测试导入或角色/主管设置失败:停止测试,不借用真实员工账号;按单步接口恢复两名测试成员原 OA 状态。
- 测试提交失败:保留 draft 和测试票据定位问题;不得改成“审批通过”绕过。修复后从同一测试草稿重试或删除后重建。
- 测试退回后删除失败:禁止停用账号,先保留两名测试账号以完成清理;不得直写数据库删除业务记录。
- 测试账号误被赋予超范围角色:立即通过现有角色接口恢复最小角色集合并撤销会话,不恢复整库。
- OCR 仍为 mock:保持测试账号停用,不让杨俊上传真实票据;不得用“人工记住忽略 Mock 字段”作为默认生产口径。当前已切换为 off,此回退项不再触发。
## 6. 隐私与审计 ## 6. 隐私与审计
@@ -85,3 +134,4 @@
- 不启用 OA → 企微通讯录写回,不修改任何企微成员资料。 - 不启用 OA → 企微通讯录写回,不修改任何企微成员资料。
- 不创建法体、不补银行账号、不导入其余成员。 - 不创建法体、不补银行账号、不导入其余成员。
- 不把首次真实业务扩成管理员代办的生产自动化测试。 - 不把首次真实业务扩成管理员代办的生产自动化测试。
- 不创建新的企微测试成员,不给测试主管财务或管理员权限,不让测试单通过审批或进入付款链路。
@@ -38,7 +38,31 @@
- [x] 运行 Trellis task/governance、流程/文档、敏感扫描和 `git diff --check`。 - [x] 运行 Trellis task/governance、流程/文档、敏感扫描和 `git diff --check`。
- [x] 按项目规则对权限、workflow 和生产写入证据做一次只读 review;Codex inline 主代理复核通过,未发现角色扩权、企微反向写入、审批自审或生产测试业务数据。 - [x] 按项目规则对权限、workflow 和生产写入证据做一次只读 review;Codex inline 主代理复核通过,未发现角色扩权、企微反向写入、审批自审或生产测试业务数据。
## 5. 用户真实验收 ## 5. 两个既有企微测试成员的受控验收
- [x] 从管理员通讯录以内存方式仅筛选“测试员工”和“测试主管”,验证二者唯一、active、资料满足导入合同;不输出或持久化原始字段。
- [x] 生产 dry-run 验收输入 2、计划创建 2、冲突/跳过/失败 0;偏差即停止。
- [x] 单独取得生产测试账号/业务写入批准后 apply,并确认两人 active、企微绑定各自唯一、没有导入其他成员。
- [x] 通过现有角色接口将角色收敛为:测试员工=`employee`,测试主管=`employee + manager`;核对 token version、会话撤销及角色审计。
- [x] 通过现有主管接口设置“测试员工 → 测试主管”,确认测试主管 active 且具有 `expense.approve`。
- [x] 两个测试成员均完成账号密码登录与首次改密,申请人可见报销入口、主管可见审批入口;企微 SSO 因不能冒充企微身份仍待人工确认。
- [x] 测试员工创建唯一一条描述含“系统测试、非真实业务、禁止付款”、总额 0.01 元的测试报销,并上传不含真实资料的安全 PDF。
- [x] 合成 PDF 经生产 mock OCR 自动生成测试字段并标记可用,绑定后成功提交;该结果同时暴露真实使用前必须移除 mock OCR 的门禁。
- [x] 测试主管确认待办可见并执行“退回”,未执行“通过”;报销回到 `draft` 且 workflow instance 终止,无需追加“撤回”。
- [x] 测试员工删除 draft 报销;票据解绑回到 `available` 后已手工归档。
- [x] 管理员停用两个测试 OA 账号并确认会话撤销、企微写回为 skipped;企微侧两个测试成员保持不变。
- [x] 只读收口确认:测试报销 0、pending 待办 0、测试票据唯一且为 manual archived、两个测试 OA 账号为 archived、活动会话 0、企微绑定保留 2;未产生测试付款。
## 5.1 真实发票路径门禁
- [x] 只读确认生产当前为 `OCR_PROVIDER=mock`,腾讯 OCR 凭证未配置。
- [x] 用户选定“关闭 mock 并采用财务查看 PDF 原件人工核对”;本轮不改代码、不部署新构建。
- [x] 生产 `.env` 已切换为 `OCR_PROVIDER=off`;现场无独立 worker 且 `JOBS_ENABLED=true`,因此仅强制重建 server。公网 `/ready` 正常,构建仍为 `4886f9b081dd`。
- [x] 重新启用两个测试 OA 账号并完成受控 API 闭环:新 PDF 直接 `available`,八个发票业务字段为空、OCR 尝试为 0,绑定、提交、主管退回均成功。
- [x] 复验后删除测试报销、归档测试票据、停用测试 OA 账号;最终活动会话 0、测试报销 0、pending 待办 0、未归档测试票据 0,企微同步均为 skipped。
- [x] 记录非阻断 schema finding:off 路径未显式写 provider,但 `ocr_provider` 因非空默认值仍存 `tencent`;请求/完成时间和 OCR 结果为空,当前 UI 不展示该字段。
## 6. 用户真实验收
- [ ] 请杨俊本人从企业微信进入 OA,确认身份解析成功并能看到报销入口。 - [ ] 请杨俊本人从企业微信进入 OA,确认身份解析成功并能看到报销入口。
- [ ] 杨俊使用真实业务数据创建报销,至少添加一条费用明细并上传/绑定状态有效的真实票据后提交。 - [ ] 杨俊使用真实业务数据创建报销,至少添加一条费用明细并上传/绑定状态有效的真实票据后提交。
@@ -46,14 +70,14 @@
- [ ] 审批通过后确认单据进入王芝玲负责的财务处理链路;后续付款批次继续遵守既有 maker-checker。 - [ ] 审批通过后确认单据进入王芝玲负责的财务处理链路;后续付款批次继续遵守既有 maker-checker。
- [ ] 用户验收期间不在聊天、截图或任务证据中粘贴票据、金额、银行资料或完整账号。 - [ ] 用户验收期间不在聊天、截图或任务证据中粘贴票据、金额、银行资料或完整账号。
## 6. 完成条件 ## 7. 完成条件
- [ ] PRD 全部验收项有脱敏证据或明确由用户完成的真实业务验收结果。 - [ ] PRD 全部验收项有脱敏证据或明确由用户完成的真实业务验收结果。
- [ ] 本任务未导入其他成员、未创建法体、未补银行资料、未生成测试生产业务数据。 - [ ] 本任务未导入杨俊和两个既有测试成员之外的其他成员、未创建法体、未补银行资料;唯一测试业务数据已按受控生命周期清理/归档。
- [ ] 若只完成系统就绪而真实报销尚未提交,任务保持未完成并明确剩余用户操作,不用合成测试替代。 - [ ] 若只完成系统就绪而真实报销尚未提交,任务保持未完成并明确剩余用户操作,不用合成测试替代。
- [ ] 完成后归档本任务,返回 R63–R66 产品主线;不从本任务继续长出新的运维加固范围。 - [ ] 完成后归档本任务,返回 R63–R66 产品主线;不从本任务继续长出新的运维加固范围。
## 7. 已完成的脱敏证据 ## 8. 已完成的脱敏证据
- timer:4 个 service 构建合同一致,4 个 timer 均 enabled/active。 - timer:4 个 service 构建合同一致,4 个 timer 均 enabled/active。
- 备份:源端真实批次 `success/0`,ARM 独立 dead-man `success/0`。 - 备份:源端真实批次 `success/0`,ARM 独立 dead-man `success/0`。
@@ -64,3 +88,7 @@
- 本地回归:5 个测试文件、61 项测试通过;Prisma schema 校验通过。 - 本地回归:5 个测试文件、61 项测试通过;Prisma schema 校验通过。
- 公网检查:`/ready` 为 ok、构建标识匹配,首页 HTTP 200。 - 公网检查:`/ready` 为 ok、构建标识匹配,首页 HTTP 200。
- 只读 review:本轮只写 OA 员工、身份绑定和 OA 主管关系;未修改角色矩阵、workflow 代码、状态机、数据库 schema 或企微成员资料。 - 只读 review:本轮只写 OA 员工、身份绑定和 OA 主管关系;未修改角色矩阵、workflow 代码、状态机、数据库 schema 或企微成员资料。
- 测试账号受控闭环:dry-run/apply 2/2、最小角色和主管关系通过;普通浏览器双账号登录、首次改密、0.01 元测试报销、合成 PDF 上传、提交、主管退回、草稿删除、票据归档均通过;最终 OA 测试账号 archived 2、活动会话 0、企微同步 skipped 2、测试报销与 pending 待办均为 0。
- finding:生产 OCR 为 mock 且真实腾讯 OCR 未配置,合成 PDF 自动生成 Mock 字段;草稿删除后页面导航前产生已删除详情/precheck 的 404 控制台噪声。前者阻断杨俊真实票据上传,后者不阻断业务。
- OCR off 复验:配置切换后未改构建;三次有业务写入的复验/诊断均按“退回 → 删除 → 归档 → 停用”收口。累计 4 张合成测试票据(含第一轮)均已归档,未归档 0;测试报销与 pending 待办均为 0。
- OCR off 结论:空发票业务字段可以绑定并提交,主管退回成功;真实使用暂由财务查看 PDF 原件并人工核对。文件 SHA-256 查重仍有效,发票号查重暂不可用。
@@ -2,7 +2,7 @@
## Goal ## Goal
让已在企业微信通讯录中的杨俊尽快具备真实报销能力:能够通过企微进入 OA、创建并提交出差后的报销,由其真实直属主管马建勇审批,再进入王芝玲负责的财务处理链路;先修复生产 timer 构建合同并取得新鲜异机恢复点,再执行最小范围的账号与主管写入。 让已在企业微信通讯录中的杨俊尽快具备真实报销能力,同时避免让真实员工承担首轮试错:先使用通讯录中既有的“测试员工”和“测试主管”两个企微测试成员,在生产 OA 完成一条受控、可清理的报销体验验收;验证通过并清理测试数据后,再请杨俊通过企微进入 OA、创建并提交真实报销,由其真实直属主管马建勇审批,再进入王芝玲负责的财务处理链路。
## Background ## Background
@@ -13,6 +13,9 @@
- 通讯录单人子集唯一、active、企业邮箱完整、手机号缺失但满足现有 nullable 合同。 - 通讯录单人子集唯一、active、企业邮箱完整、手机号缺失但满足现有 nullable 合同。
- 当前有 37 个 active 费用类型和 1 个 active 报销工作流定义;active 法体为 0,但代码确认报销创建/提交不读取法体,法体不是本次硬门禁。 - 当前有 37 个 active 费用类型和 1 个 active 报销工作流定义;active 法体为 0,但代码确认报销创建/提交不读取法体,法体不是本次硬门禁。
- 当前自动备份 daily/watchdog/drill-reminder 和日常巡检 service 的 BUILD_SHA 均与活动 server 不一致;最近一次备份成功发生在该漂移被今晚任务触发之前,不能视为今晚安全。 - 当前自动备份 daily/watchdog/drill-reminder 和日常巡检 service 的 BUILD_SHA 均与活动 server 不一致;最近一次备份成功发生在该漂移被今晚任务触发之前,不能视为今晚安全。
- 管理员导出的通讯录中已有“测试员工”和“测试主管”两个专用企微测试成员;二者均有唯一账号、部门及联系方式且状态为已激活,但生产 OA 尚无姓名包含“测试”的员工或绑定。
- 受控测试发现生产 `OCR_PROVIDER=mock` 且腾讯 OCR 凭证未配置;合成 PDF 被写入虚构的 Mock 发票字段并直接标记可用。该行为可用于测试,但不得作为杨俊真实发票的正式识别结果。
- 生产已将 `OCR_PROVIDER` 切换为 `off` 并重建实际承载后台任务的 server;复验确认票据不进入 OCR 队列、业务字段为空且仍可绑定和提交。`ocr_provider` 因既有非空 schema 默认值仍记录为 `tencent`,但 OCR 尝试数和请求/完成时间均为空;该历史字段语义不作为“已调用腾讯 OCR”的证据。
## Requirements ## Requirements
@@ -22,14 +25,20 @@
- 通讯录导入只允许把企微管理员导出的成员资料写入 OA 并建立身份绑定;OA 不调用企微成员 create/update/disable,不反向修改企微姓名、部门、主管、手机号或邮箱。企微成员资料由本人在企微或企微管理员在管理后台维护。 - 通讯录导入只允许把企微管理员导出的成员资料写入 OA 并建立身份绑定;OA 不调用企微成员 create/update/disable,不反向修改企微姓名、部门、主管、手机号或邮箱。企微成员资料由本人在企微或企微管理员在管理后台维护。
- 将杨俊的 `direct_manager_id` 设置为用户已确认的真实直属主管马建勇;写入前仍需只读确认马建勇为不同、active 且具有 `expense.approve` 权限的 OA 员工,不得为了报销绕过审批或扩宽角色。 - 将杨俊的 `direct_manager_id` 设置为用户已确认的真实直属主管马建勇;写入前仍需只读确认马建勇为不同、active 且具有 `expense.approve` 权限的 OA 员工,不得为了报销绕过审批或扩宽角色。
- 若费用类型或报销工作流缺失,只补足完成报销所需的最小主数据;不为了本次报销新建法体,不顺带整理全部组织架构或导入其余员工。 - 若费用类型或报销工作流缺失,只补足完成报销所需的最小主数据;不为了本次报销新建法体,不顺带整理全部组织架构或导入其余员工。
- 写入后只做账号、绑定、主管解析、费用类型和提交流程的安全验证;不创建虚假生产报销、发票或付款数据。 - 杨俊写入后只做账号、绑定、主管解析、费用类型和提交流程的安全验证;杨俊本人名下不创建测试报销、测试发票或测试付款数据。
- 允许从同一份管理员通讯录中仅筛选既有的“测试员工”和“测试主管”两个企微测试成员,先 dry-run 再 apply;不得新建企微成员,不得把其他真实成员带入输入。
- 两名测试成员进入 OA 后,“测试员工”角色严格为 `employee`,“测试主管”角色严格为 `employee + manager`;测试员工的 OA 直属主管仅指向测试主管。不得授予 `finance`、`finance_mgr`、`administration` 或 `admin`,不得借测试查看真实薪资、银行资料或系统管理数据。
- 每次受控验证最多创建一条明确标记“系统测试、非真实业务、禁止付款”的测试报销和一张不含真实票据、银行或个人资料的安全测试 PDF;金额使用最小测试值,测试单不得审批通过或进入财务付款链路。配置复验或固定门禁诊断需要重跑时,每批都必须独立退回、删除报销并归档票据。
- 受控测试必须覆盖:两个测试成员分别通过企微登录、申请人创建/编辑报销、上传安全 PDF、处理 OCR 失败分支(如发生)、绑定票据、提交、主管待办可见及执行“退回”。退回后申请人删除 draft 报销,释放后的测试票据手工归档。
- 测试结束后撤销两个测试成员的 OA 会话并停用其 OA 账号;仅停用 OA,不修改或停用企微中的两个测试成员。后续需要复测时可通过现有 OA 接口重新启用。
- 杨俊上传真实发票前必须消除生产 mock OCR:要么配置并验证真实腾讯 OCR,要么显式关闭 mock 并采用人工录入/核对方案;不得把 Mock 字段展示为真实识别结果。
- 真实报销提交前至少需要一条费用明细和一张已绑定、状态有效的票据;这由员工在真实业务中提供,不用合成数据代替。 - 真实报销提交前至少需要一条费用明细和一张已绑定、状态有效的票据;这由员工在真实业务中提供,不用合成数据代替。
- 最近部署后必须重装自动备份三项 service/timer 和日常巡检 timer,使 unit BUILD_SHA 与活动 server 一致;重装后手工触发一次真实自动异机备份,确认源端成功且 ARM 恢复点新鲜,再允许写入杨俊账号或主管关系。 - 最近部署后必须重装自动备份三项 service/timer 和日常巡检 timer,使 unit BUILD_SHA 与活动 server 一致;重装后手工触发一次真实自动异机备份,确认源端成功且 ARM 恢复点新鲜,再允许写入杨俊账号或主管关系。
- 第一笔真实业务由员工本人正常发起,目标链路为“员工提交 → 真实主管审批 → 财务处理”;付款批次仍遵守既有 maker-checker。 - 第一笔真实业务由员工本人正常发起,目标链路为“员工提交 → 真实主管审批 → 财务处理”;付款批次仍遵守既有 maker-checker。
## Scope ## Scope
本轮范围只包括:修复生产 automatic-backup/WARCH3 timer 的构建合同并取得新鲜异机恢复点;从通讯录中仅导入杨俊为 `employee`;将杨俊的真实直属主管设置为马建勇;验证企微登录和现有报销链路已就绪。所有真实报销内容由杨俊本人提供和提交,所有审批与财务处理由对应真实人员完成。 本轮范围只包括:修复生产 automatic-backup/WARCH3 timer 的构建合同并取得新鲜异机恢复点;从通讯录导入杨俊及两个既有企微测试成员;设置各自真实或测试主管关系;用两个测试成员完成一条可清理的生产报销体验验收;清理测试业务数据并停用测试 OA 账号;最后由杨俊本人使用真实数据发起报销。所有真实报销内容由杨俊本人提供和提交,所有真实审批与财务处理由对应真实人员完成。
## Acceptance Criteria ## Acceptance Criteria
@@ -41,7 +50,13 @@
- [x] 自动备份三项 timer 与日常巡检 timer 的 BUILD_SHA 均与活动 server 一致,且 timer 保持 enabled/active。 - [x] 自动备份三项 timer 与日常巡检 timer 的 BUILD_SHA 均与活动 server 一致,且 timer 保持 enabled/active。
- [x] timer 修复后生成一份新的成功异机批次,ARM 侧完整性/新鲜度检查通过;该门禁先于任何杨俊生产数据写入。 - [x] timer 修复后生成一份新的成功异机批次,ARM 侧完整性/新鲜度检查通过;该门禁先于任何杨俊生产数据写入。
- [ ] 目标员工完成一次真实企微登录并能看到报销入口;主管能看到相应审批入口。 - [ ] 目标员工完成一次真实企微登录并能看到报销入口;主管能看到相应审批入口。
- [x] 不持久化通讯录筛选文件,不导入其他成员,不产生测试报销、测试发票或测试付款。 - [ ] 两个既有企微测试成员分别成功通过企微登录 OA;没有创建新的企微成员。
- [x] 两个测试成员均通过普通浏览器账号密码登录并完成首次改密;企微 SSO 仍保留为人工确认项。
- [x] 测试员工仅有 `employee`,测试主管仅有 `employee + manager`,测试主管能看到测试员工提交的待办且执行“退回”。
- [x] 每次受控验证最多产生一条带明确测试标识的报销和一张安全测试 PDF;所有批次均未审批通过、未进入财务或付款链路,并已逐批清理。
- [x] 退回后的测试报销已删除,测试票据已解绑并归档,两个测试 OA 账号已停用且会话已撤销;企微测试成员未被修改或停用。
- [x] 不持久化通讯录筛选文件,不导入杨俊及两个测试成员之外的其他成员,不产生测试付款。
- [x] 生产已显式关闭 mock OCR;`off` 路径已用测试账号复验,空发票元数据仍可绑定、提交、主管退回和清理,真实使用暂由财务查看 PDF 原件并人工核对。
- [x] 本轮没有调用企微成员写接口;企微成员资料未被 OA 反向修改。 - [x] 本轮没有调用企微成员写接口;企微成员资料未被 OA 反向修改。
## Out of Scope ## Out of Scope
@@ -51,6 +66,7 @@
- 不从 OA 创建、更新或停用企微成员;需要修改企微成员资料时,由本人或企微管理员在企微侧处理。 - 不从 OA 创建、更新或停用企微成员;需要修改企微成员资料时,由本人或企微管理员在企微侧处理。
- 不提前实现 R63–R66,不扩大为全员入职或组织架构治理。 - 不提前实现 R63–R66,不扩大为全员入职或组织架构治理。
- 不在未获单独批准时执行通讯录 apply、主管/法体写入、生产报销或付款操作。 - 不在未获单独批准时执行通讯录 apply、主管/法体写入、生产报销或付款操作。
- 不使用真实发票、真实金额、真实银行资料或真实员工个人资料作为测试材料;不让测试单进入“通过/待财务/付款”状态。
- 不把手机号缺失视为导入阻断项;该员工使用企微或企业邮箱登录,后续按需补手机号。 - 不把手机号缺失视为导入阻断项;该员工使用企微或企业邮箱登录,后续按需补手机号。
## Test Plan ## Test Plan
+2 -1
查看文件
@@ -76,6 +76,7 @@
- [ ] `docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env exec -T server node src/scripts/smoke-external-deps.js` - [ ] `docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env exec -T server node src/scripts/smoke-external-deps.js`
- [ ] 若 external deps smoke 通过,已确认通过前提不是“企微关闭 + OCR mock”误通过 - [ ] 若 external deps smoke 通过,已确认通过前提不是“企微关闭 + OCR mock”误通过
- [ ] 若启用腾讯 OCR,已脱敏核查 `OCR_PROVIDER=tencent`、`TENCENT_SECRET_ID` 长度、`TENCENT_SECRET_KEY` 长度和 `TENCENT_OCR_REGION` - [ ] 若启用腾讯 OCR,已脱敏核查 `OCR_PROVIDER=tencent`、`TENCENT_SECRET_ID` 长度、`TENCENT_SECRET_KEY` 长度和 `TENCENT_OCR_REGION`
- [ ] 若真实 OCR 未配置,已使用 `OCR_PROVIDER=off` 而不是 mock,并确认实际作业拓扑中的 server/worker 已重建;真实票据由财务查看 PDF 原件人工核对
- [ ] 若本次包含前端体验修复,已在容器内确认 `/app/client/dist` 含关键 test id 或公网 asset hash 已更新 - [ ] 若本次包含前端体验修复,已在容器内确认 `/app/client/dist` 含关键 test id 或公网 asset hash 已更新
- [ ] 公网 bundle 扫描确认包含匹配的 12 位构建标识,且不包含完整提交 SHA - [ ] 公网 bundle 扫描确认包含匹配的 12 位构建标识,且不包含完整提交 SHA
- [ ] 登录验证已由 live smoke 与 release gate 覆盖;仅首次接入或第三方后台配置变更时补做人工确认 - [ ] 登录验证已由 live smoke 与 release gate 覆盖;仅首次接入或第三方后台配置变更时补做人工确认
@@ -102,7 +103,7 @@
- [ ] 重启并探活: - [ ] 重启并探活:
- [ ] `docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server` - [ ] `docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server`
- [ ] 已重新用 `docker-compose -p "$COMPOSE_PROJECT_NAME" ... port server 3000` 解析实际宿主端口并执行 `/ready` - [ ] 已重新用 `docker-compose -p "$COMPOSE_PROJECT_NAME" ... port server 3000` 解析实际宿主端口并执行 `/ready`
- [ ] 若仅为 OCR 应急降级,已把远端 `.env` 的 `OCR_PROVIDER` 临时切回 `mock`,重启 server,并在 Issue/发布记录中写明原因、影响范围和恢复时间 - [ ] 若仅为 OCR 应急降级,已把远端 `.env` 的 `OCR_PROVIDER` 切为 `off`,按实际作业拓扑重建 server/worker,并记录人工核对责任;不得把 mock 用于真实票据
+5 -4
查看文件
@@ -187,10 +187,11 @@ E2E_BASE_URL=https://oa.hyinda.com \
npx playwright test tests/e2e/manual/live-invoice-ocr.spec.ts --workers=1 --reporter=list --timeout=360000 npx playwright test tests/e2e/manual/live-invoice-ocr.spec.ts --workers=1 --reporter=list --timeout=360000
``` ```
回滚到 mock 只允许作为应急降级: 真实 OCR 不可用时降级到 off;mock 仅用于本地或明确隔离的合成测试:
1. 在远端 `.env` 将 `OCR_PROVIDER=tencent` 改为 `OCR_PROVIDER=mock`。 1. 在远端 `.env` 将 `OCR_PROVIDER=tencent` 改为 `OCR_PROVIDER=off`。
2. 执行 `docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d server`。 2. 按现场作业拓扑重建服务:`JOBS_ENABLED=true` 且无独立 worker 时只重建 server;已有独立 worker 时同时重建 server 和 worker,不能凭空新增第二个作业执行器。
3. 在 Issue/发布记录中写明降级原因、预计恢复时间和影响范围。 3. 验证新 PDF 直接进入 `available`、业务字段为空且 OCR 尝试为 0,并确认财务改为查看 PDF 原件人工核对。
4. 在当前 Trellis task 或发布记录中写明降级原因、预计恢复时间和影响范围。
## 1.5 备份与恢复演练(正式启用前必须完成) ## 1.5 备份与恢复演练(正式启用前必须完成)
> 数据库和文件存储必须使用同一批次成对备份;只有两份产物、完整性检查和 manifest 全部成功,才算一个可用恢复点。 > 数据库和文件存储必须使用同一批次成对备份;只有两份产物、完整性检查和 manifest 全部成功,才算一个可用恢复点。
+4
查看文件
@@ -43,6 +43,10 @@ R5 公网验收后确认:正式发票/OCR 上传入口只接受 PDF 发票文
- OCR 完成后,添加发票弹窗应自动刷新并尝试添加可用发票。 - OCR 完成后,添加发票弹窗应自动刷新并尝试添加可用发票。
- OCR 失败、重复、已使用、他人已使用都要显示可理解原因。 - OCR 失败、重复、已使用、他人已使用都要显示可理解原因。
- 刷新按钮必须有明显反馈,不能让用户误以为没点中。 - 刷新按钮必须有明显反馈,不能让用户误以为没点中。
- 正式环境不得使用 `OCR_PROVIDER=mock` 处理真实票据;mock 仅用于本地或明确隔离的合成测试。
- 未配置真实 OCR 时使用 `OCR_PROVIDER=off`:PDF 直接进入 `available`,不入 OCR 队列、不生成发票代码、号码、日期、购销方或金额。员工仍可把票据添加到已有明细并提交,财务必须查看 PDF 原件人工核对。
- off 模式下文件 SHA-256 查重继续有效,依赖发票代码和号码的查重暂不可用。对当前小团队可短期接受;强制人工补录或接入腾讯 OCR 应另行规划,不能在紧急报销中临时增加门槛。
- 既有 `InvoicePool.ocr_provider` 为非空枚举且默认 `tencent`,所以 off 模式的新记录仍可能带该历史默认标记。判定是否实际调用 OCR 必须同时看 provider 配置、`ocr_attempts`、请求/完成时间和 OCR 结果,不能只看 `ocr_provider`。
## 样票脱敏和授权 ## 样票脱敏和授权