完善差旅补助规定与费用类型(07-28-travel-reimbursement-guide)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-07-29 20:04:35 +08:00
父节点 5df5f12ad6
当前提交 13a3f4b57d
共修改 10 个文件,包含 201 行新增和 15 行删除
@@ -383,6 +383,96 @@ node scripts/qa/check-ocr-provider-samples.mjs \
The caller records stdout before applying the exit-code gate; it never uses a
`set -e` assignment whose non-zero status exits before the JSON is emitted.
## Scenario: Travel Allowance Expense-Type Boundary
### 1. Scope / Trigger
- Trigger: adding or changing travel reimbursement types, travel-request
transport mappings, allowance policies or expense standards.
- Purpose: keep intercity transport reimbursed by evidence separate from the
two daily lump-sum allowances, and avoid false over-limit results until the
reimbursement item model supports a day count.
### 2. Signatures
- Seed contract in `server/src/scripts/seed-config.js`:
- `travel_bus` -> `大巴车票`.
- `travel_meal` -> `伙食补助费`.
- `travel_local_transport_allowance` -> `市内交通补助费`.
- Travel conversion contract in `server/src/routes/reimbursements.js`:
`TRAVEL_TRANSPORT_EXPENSE_TYPE_CODES.bus === "travel_bus"`.
- Current item data boundary: `ReimbursementItem` stores expense type, date,
amount and description, but no quantity or eligible-day count.
### 3. Contracts
- `travel_bus` always means an intercity bus/coach ticket created from or used
with a travel segment. It must not represent local transport allowance.
- Meal allowance is CNY 100 per eligible person-day; local transport allowance
is CNY 80 per eligible person-day. Both are lump-sum items without an invoice.
- Employees create one item per allowance, set the amount to eligible days
multiplied by the rate, and write the date range, day count, rate and formula
in the description. Supervisor and finance review that calculation.
- Departure and return dates each count as one natural day; same-day return is
one day. Personal extensions do not count. Meals or vehicles paid directly
by the company or another organization exclude the corresponding allowance.
- Claiming meal allowance excludes separate personal meal reimbursement;
claiming local transport allowance excludes bus, metro, taxi and ride-hailing
reimbursement during the trip. Intercity transport, accommodation and other
approved necessary expenses remain evidence-based actual reimbursement.
- `ExpenseStandard.per_unit=day` currently does not multiply `max_amount` by a
day count. Do not seed or configure CNY 100/CNY 80 warn/block standards until
item quantity/day multiplication is implemented end to end.
### 4. Validation & Error Matrix
- Travel segment `bus` maps to an allowance type -> block the change; preserve
`travel_bus` and add/use the separate allowance code.
- CNY 100 or CNY 80 daily rate configured as the current item-level maximum ->
remove/disable it because a multi-day allowance would be falsely over limit.
- Allowance item lacks date range, eligible-day count, rate or formula -> return
for completion during supervisor/finance review; do not invent a day count.
- Company/other party directly bore the corresponding cost -> exclude those
days from the applicable allowance and record the reason.
### 5. Good/Base/Bad Cases
- Good: a three-day eligible trip uses separate meal and local-transport items
whose descriptions show `3 天 × 单价`, while rail/hotel items carry PDFs and
required payment evidence.
- Base: intercity coach travel uses `travel_bus` / `大巴车票` and remains an
actual-expense item with valid evidence.
- Bad: rename `travel_bus` to local transport allowance, or configure a CNY 80
item maximum and thereby reject a valid multi-day allowance.
### 6. Tests Required
- Seed-default unit test asserts all three codes and Chinese names are distinct.
- Travel-request conversion test asserts `transport_type=bus` produces an item
whose expense type code is `travel_bus` and description identifies `大巴`.
- When automatic day calculation is later implemented, add service and route
tests for same-day, multi-day, personal-extension and directly-borne cases
before enabling any daily standard.
- Documentation checks must keep the employee guide, reimbursement fact source
and production expense-type names aligned.
### 7. Wrong vs Correct
#### Wrong
```js
const TRAVEL_TRANSPORT_EXPENSE_TYPE_CODES = { bus: "travel_local_transport_allowance" };
// Current fee control compares the whole multi-day item directly with 80.
```
#### Correct
```js
const TRAVEL_TRANSPORT_EXPENSE_TYPE_CODES = { bus: "travel_bus" };
// Local transport allowance is a separate manually calculated item until the
// model and fee-control service both support eligible-day multiplication.
```
## Scenario: Payment Batch Receipt Attention Count
### 1. Scope / Trigger
@@ -2,7 +2,7 @@
## 边界
本任务不开发新的 OCR provider,也不开放图片发票或改变审批流。在现有腾讯 provider 内把统一入口从 `VatInvoiceOCR` 改为 `RecognizeGeneralInvoice`,增加票种感知的安全归一化和上传大小门禁,再完成生产配置恢复、真实链路验证及两份既有业务文档更新。
本任务不开发新的 OCR provider,也不开放图片发票或改变审批流。在现有腾讯 provider 内把统一入口从 `VatInvoiceOCR` 改为 `RecognizeGeneralInvoice`,增加票种感知的安全归一化和上传大小门禁,再完成生产配置恢复、真实链路验证、差旅补助费用类型收口及两份既有业务文档更新。
生产配置写入、容器重建、腾讯 API 调用和真实样票上传均属于单独受控步骤;每一步只在前一步通过后继续。
@@ -60,6 +60,14 @@
- `docs/报销发票附件规则.md` 固化支付证明必交、敏感字段遮挡、退款/代付/优惠差异和无截图例外口径。
- OCR 结果仅辅助识别和查重;财务仍核对明细、PDF 原件和个人支付证明。
## 差旅补助与费用类型
- `travel_bus` 已由差旅行程 `transport_type=bus` 自动映射,业务语义是城际“大巴”;将其显示名收口为“大巴车票”,不得复用为市内交通补助。
- `travel_meal` 保留代码并将显示名收口为“伙食补助费”;新增 `travel_local_transport_allowance`,显示名为“市内交通补助费”。生产当前两类既有费用均无历史报销明细,种子脚本按代码幂等更新名称和新增类型。
- 伙食补助费为 100 元/人/天,市内交通补助费为 80 元/人/天,均按自然日包干且不要求发票;直接承担、重复报销和个人延长天数按 PRD 规则处理。
- 当前 `ReimbursementItem` 没有天数或数量字段,`ExpenseStandard.per_unit=day` 也不会在费用控制中乘以天数。因此本轮不创建 100/80 元 block 或 warn 标准,员工分别新增补助明细,以“天数 × 单价”人工算出总额并在说明写明计算式,由主管和财务复核。
- 自动带出差旅起止日、计算符合条件的补助天数以及按天校验属于后续产品能力,不阻塞本次真实报销。
## 失败与回退
- adapter 聚焦测试、配置 smoke、腾讯调用或任一真实样票验证失败:停止开放真实票据,不继续制造 pending/retry 数据。
@@ -62,6 +62,14 @@
- [x] 更新 `docs/报销发票附件规则.md` 的支付截图、实报实销差异、退款/代付和人工复核规则。
- [x] 不新建平行员工指南,不写入真实票据、支付信息、账号或环境路径。
## 5.1 差旅补助规定落地
- [x] 修正 `travel_bus` 显示名为“大巴车票”,修正 `travel_meal` 显示名为“伙食补助费”,新增 `travel_local_transport_allowance` / “市内交通补助费”。
- [x] 增加聚焦测试,锁定城际大巴映射和两项补助费用类型不混用。
- [x] 更新员工指南和报销事实源,写明 100 元/天伙食补助、80 元/天市内交通补助、自然日、包干、直接承担、个人延长和其他费用实报实销规则。
- [x] 明确当前由员工按“天数 × 单价”人工填写总额;不配置会误判多日补助的单条费用标准。
- [x] 重新生成并检查一页版差旅报销 PDF。
## 6. 验证与审查
- [x] 静态核对页面/API 上传格式、明细绑定和员工/财务字段边界。
@@ -115,6 +123,13 @@
- 仅在铁路通过后运行住宿样票并明确 `PASS`:票种为数电发票,号码、日期、金额、税额、价税合计、销售方和购买方字段命中,无缺失必填字段。
- 两次授权腾讯 OCR 调用已全部用完。输出仅保留匿名 alias、票种和字段命中布尔值;两次探针均未创建 OA 发票或 OCR 数据,最近 30 分钟对应新增记录为 `0/0`,无需业务数据清理。
## 2026-07-29 差旅补助规定实施验证
- 已把城际大巴、伙食补助和市内交通补助收口为三个独立稳定代码;种子聚焦测试证明按代码幂等更新/创建,不配置 100/80 元单条费用标准。
- 员工指南与报销事实源已写明两项补助单价、自然日、包干、直接承担不重复领取、个人延长不计补助和人工算式要求;单页 PDF 已重新生成并完成视觉检查。
- 聚焦测试 3 个文件/17 项、后端全量 168 个文件/1077 项、Prisma validate、流程合同、文档审计、Trellis governance、敏感内容、task validate、Node 语法和 `git diff --check` 均通过;文档审计仅保留 22 个既有未注册文档 warning。
- 本地实施不等于生产费用类型已变更。提交、推送、部署和生产 `seed:config` 仍等待对应授权;在生产只读复核三个费用类型且确认未新增错误费用标准前,不通知杨俊按补助新口径提交。
## 风险文件与操作
- 生产环境文件:包含 Secret,只允许用户现场静默输入;不得由补丁、聊天或命令参数传入。
@@ -2,7 +2,7 @@
## Goal
先把生产发票识别从 mock 切换到真实腾讯 OCR 并取得授权样票证据,再让杨俊及后续员工一次看懂差旅报销需要提交什么、分别上传到哪里,以及财务如何按“明细—发票—个人支付证明”复核。
先把生产发票识别从 mock 切换到真实腾讯 OCR 并取得授权样票证据,再让杨俊及后续员工一次看懂差旅报销需要提交什么、分别上传到哪里、差旅补助如何计算,以及财务如何按“明细—发票—个人支付证明”复核。
## Background
@@ -26,6 +26,7 @@
- 更新 `docs/报销发票附件规则.md`,固化火车票/住宿费支付证明、实报实销和 OCR 关闭期间人工核对规则。
- 配置并正式启用生产腾讯 OCR,完成配置门禁、受控发布、公网检查和授权 PDF 样票验证。
- 在现有腾讯 provider 内改用通用票据识别高级版,补齐住宿电子普通发票和铁路电子客票的安全归一化、质量门禁、大小限制和测试。
- 固化公司差旅补助规定,补齐不与城际大巴费用混淆的费用类型,并同步员工指南和报销事实源。
## Requirements
@@ -47,6 +48,12 @@
- 当前无独立 worker 且 server 自身运行 jobs,配置变更只重建实际加载配置的 server;不得额外启动第二个 OCR job executor。
- 真实能力必须使用已授权 PDF 样票验证;缺少授权样票只能证明配置齐全,不能宣称真实 OCR 已通过。
- 指南明确审批链:员工提交、直属主管审批、财务复核;财务付款和回单不由员工预填。
- 伙食补助费按 100 元/人/天包干,市内交通补助费按 80 元/人/天包干;两项补助均不要求发票。
- 补助按出差自然日计算,出发日和返回日均计 1 天,当天往返计 1 天;因个人原因延长的天数不计补助。
- 领取伙食补助后个人餐费不得另报;领取市内交通补助后,出差期间公交、地铁、出租车、网约车等不得另报。
- 公司或其他单位已直接承担餐食或车辆费用的,不重复领取相应补助;城际交通、住宿及其他经批准的必要差旅费用凭有效材料实报实销。
- 当前报销明细没有天数字段,两项补助分别作为独立明细人工填写总额和计算式,例如“伙食补助费:3 天 × 100 元”;不得把现有 `per_unit=day` 费用标准直接配置为 100/80 元,因为当前校验不会按天数乘算,会误判多日补助。
- `travel_bus` 保留为城际大巴车票费用;市内交通补助使用独立费用类型,不得共用代码或混同展示。
- 文档不放真实票据、支付截图、账号、订单号、银行信息或完整业务数据。
## Acceptance Criteria
@@ -59,6 +66,9 @@
- [ ] 生产实际 provider 为 `tencent`,只授予 `ocr:RecognizeGeneralInvoice`,两个凭证字段已配置但不泄露,external deps smoke、`/ready` 和运行 job 拓扑通过。
- [ ] 住宿电子普通发票和铁路电子客票各一份已授权 PDF 经真实腾讯 OCR 进入可解释终态,并证明没有调用 mock;样票和原始识别 payload 不进入仓库或聊天。
- [ ] 明确 OCR 只辅助字段识别/查重,财务仍执行明细—PDF 原件—支付证明复核。
- [ ] 员工指南和报销事实源完整写明两项补助的单价、自然日口径、包干不另报、直接承担不重复领取和个人延长不计补助规则。
- [ ] 生产种子数据中“伙食补助费”“市内交通补助费”均为独立 enabled 费用类型,`travel_bus` 仍明确表示城际大巴车票。
- [ ] 未配置会把多日补助误判为超标的 100/80 元单条费用标准;指南要求员工按天数人工计算并在说明中写出算式。
- [ ] 在真实 OCR 验收完成前不得仅因指南已完成就通知杨俊可以提交。
- [ ] 全程不把真实票据、支付截图、完整账号、银行资料、Secret 或原始 payload 写入仓库、任务或聊天。
@@ -66,9 +76,9 @@
- 新增第二个 OCR provider、开放图片发票、自动验真或增加人工发票字段。
- 本轮不做通用发票平台或覆盖全部票种;仅支持现有住宿 VAT/电子普通发票和电子发票(铁路电子客票)主路径。
- 修改报销页面、上传格式、审批规则、费用标准或费用类型。
- 修改报销页面、上传格式、审批规则或自动按天计算;本轮只调整差旅相关费用类型名称并新增独立的市内交通补助类型,不配置 100/80 元费用标准。
- 代替杨俊上传票据、创建报销、提交审批,或代替主管/财务处理。
- 制定覆盖所有费用类别的完整财务制度;本轮只收口首笔差旅的火车票和住宿费。
- 制定覆盖所有费用类别的完整财务制度;本轮只收口首笔差旅的实报实销材料和两项包干补助。
## Test Plan