完善差旅补助规定与费用类型(07-28-travel-reimbursement-guide)
这个提交包含在:
@@ -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
|
||||
|
||||
|
||||
+8
-5
@@ -49,18 +49,21 @@
|
||||
- OAOPS1 期间请用真实业务数据完成报销、采购/费用、合同台账和项目关联;遇到提交、审批、付款、导出或权限阻断时,记录页面、步骤和影响等级,让管理员当天建修复合同或暂停对应流程。
|
||||
- OALAUNCH1 收口期内,请把真实业务问题按“阻断 / 非阻断 / 建议”告诉管理员;不要只在聊天里说“系统有问题”。阻断包括无法提交、无法审批、付款/AP 无法闭环、越权可见、合同或资产敏感信息泄露、备份恢复或导出证据缺失。
|
||||
|
||||
### 差旅报销一页指南(火车票 + 住宿费)
|
||||
### 差旅报销一页指南
|
||||
|
||||
1. 从企业微信工作台进入海音达工作台,打开 `报销`,新建报销单。
|
||||
2. 把费用拆成两条明细,不要合并填写:
|
||||
2. 把费用拆成独立明细,不要合并填写:
|
||||
- 火车票:费用类型选“火车票”,填写乘车日期、个人实际支付的合规金额和事由,例如“项目出差往返高铁”。
|
||||
- 住宿费:费用类型选“住宿费”,填写实际住宿日期、个人实际支付的合规金额和事由,例如“项目出差住宿”。
|
||||
- 伙食补助费:按符合条件的出差自然日 `N 天 × 100 元` 计算总额,说明中写明天数、日期范围和算式,不要求发票。
|
||||
- 市内交通补助费:按符合条件的出差自然日 `N 天 × 80 元` 计算总额,说明中写明天数、日期范围和算式,不要求发票。
|
||||
3. 在每条明细的“添加发票”入口上传对应的官方 PDF:火车电子票据 PDF 绑定火车票明细,住宿电子普通发票 PDF 绑定住宿费明细。每个 PDF 只放一张票据、只识别第一页,单个文件不能超过 7 MiB;图片、照片和支付截图不能当作正式发票上传。
|
||||
4. 火车票和住宿费都必须另附个人支付截图。支付截图通过整张报销单的“上传附件 / 补充材料”入口上传,并在文件名或说明中写清对应“火车票”还是“住宿费”。
|
||||
5. 支付截图应看得出商户、金额、支付时间和支付成功状态;银行卡号、支付账号、订单号等与复核无关的信息先遮挡。
|
||||
6. 明细金额填写个人实际支付的合规金额,原则上不得超过有效发票金额。使用优惠券、多人合并开票、部分自费或发票金额与实际支付金额不一致时,必须在明细说明中写清原因。
|
||||
7. 已退款、公司直接支付或他人承担的金额不能再次报销。确实无法提供支付截图时,上传情况说明,由财务决定退回还是例外接受,不得补造凭证。
|
||||
8. 提交前逐项检查:两条明细、两份对应 PDF、两类支付证明和金额说明都已齐全。提交后由直属主管审批、财务复核;实际付款和付款回单由财务处理,员工不预填。
|
||||
6. 补助按自然日计算:出发日、返回日各计 1 天,当天往返计 1 天;因个人原因延长的天数不计。公司或其他单位已直接承担餐食或车辆费用的,对应补助不得重复领取。
|
||||
7. 伙食补助和市内交通补助均为包干:领取伙食补助后个人餐费不得另报;领取市内交通补助后,出差期间公交、地铁、出租车、网约车等不得另报。城际交通、住宿及其他经批准的必要差旅费用凭有效材料实报实销。
|
||||
8. 实报实销明细填写个人实际支付的合规金额,原则上不得超过有效发票金额。优惠券、多人合并开票、部分自费或发票金额与实付金额不一致时,必须说明;已退款、公司直接支付或他人承担的金额不能再次报销。无法提供支付截图时上传情况说明,由财务决定是否例外接受,不得补造凭证。
|
||||
9. 提交前检查各项明细、对应 PDF、火车票和住宿费支付证明以及补助算式。提交后由直属主管审批、财务复核;实际付款和付款回单由财务处理,员工不预填。
|
||||
|
||||
OCR 会辅助识别发票字段和查重,但不代表系统已经确认费用真实合规。财务仍会同时核对明细金额、PDF 原件和个人支付证明;三者不一致时会退回补充或更正。
|
||||
|
||||
|
||||
+11
-1
@@ -133,7 +133,7 @@ R5 公网验收后确认:正式发票/OCR 上传入口只接受 PDF 发票文
|
||||
|
||||
## 差旅实报实销与支付证明
|
||||
|
||||
首笔差旅报销按“明细—正式发票—个人支付证明”三方对应复核,当前火车票和住宿费执行以下规则:
|
||||
首笔差旅报销按“实报实销明细—正式票据—个人支付证明”和“包干补助—天数—单价”分别复核。
|
||||
|
||||
- 火车票和住宿费必须拆成不同明细,分别选择生产已有的“火车票”和“住宿费”费用类型,填写发生日期、个人实际支付的合规金额和事由。
|
||||
- 火车电子票据、住宿电子普通发票等正式票据必须使用官方 PDF,从对应明细的发票入口上传并绑定;发票截图、支付截图或拍照图片不得冒充正式发票。
|
||||
@@ -145,6 +145,16 @@ R5 公网验收后确认:正式发票/OCR 上传入口只接受 PDF 发票文
|
||||
- OCR 只辅助识别发票字段、匹配和查重,不判断支付真实性、费用合理性或发票法律状态。财务复核时必须同时查看明细金额、PDF 原件和个人支付证明;三者不一致时退回更正或补充说明。
|
||||
- 财务付款凭证号、付款日期、付款渠道和付款回单属于审批通过后的财务处理,不是员工提交报销时填写的个人支付证明。
|
||||
|
||||
### 差旅补助规定
|
||||
|
||||
- 伙食补助费为 100 元/人/天,市内交通补助费为 80 元/人/天,均按符合条件的出差自然日包干发放,不要求发票。
|
||||
- 出发日和返回日均计 1 天,当天往返计 1 天;因个人原因延长的行程天数不计补助。
|
||||
- 领取伙食补助后,个人餐费不得另行报销;领取市内交通补助后,出差期间公交、地铁、出租车、网约车等市内交通费用不得另行报销。
|
||||
- 公司或其他单位已直接承担相应餐食或车辆费用的,不重复领取对应补助。
|
||||
- 城际交通、住宿以及其他经批准的必要差旅费用凭有效材料实报实销。城际大巴费用使用“大巴车票”费用类型,不得填写为“市内交通补助费”。
|
||||
- 当前系统不自动计算补助天数。员工应为两项补助分别建立明细,金额填写 `符合条件的天数 × 对应单价` 的总额,并在说明中写明日期范围、天数、单价、算式及不计补助的情况;主管和财务据此复核。
|
||||
- 在系统具备数量/天数乘算能力前,不配置 100 元或 80 元的单条费用上限。现有按日单位只作展示,直接配置会把多日补助误判为超标。
|
||||
|
||||
## PC 与手机体验
|
||||
|
||||
PC 端:
|
||||
|
||||
@@ -7,8 +7,13 @@ const ENABLED_EXPENSE_TYPES = [
|
||||
{ code: "travel_train", name: "火车票", category: "travel" },
|
||||
{ code: "travel_taxi", name: "打车费", category: "travel" },
|
||||
{ code: "travel_hotel", name: "住宿费", category: "travel" },
|
||||
{ code: "travel_bus", name: "市内交通", category: "travel" },
|
||||
{ code: "travel_meal", name: "差旅餐补", category: "travel" },
|
||||
{ code: "travel_bus", name: "大巴车票", category: "travel" },
|
||||
{ code: "travel_meal", name: "伙食补助费", category: "travel" },
|
||||
{
|
||||
code: "travel_local_transport_allowance",
|
||||
name: "市内交通补助费",
|
||||
category: "travel",
|
||||
},
|
||||
{ code: "travel_fuel", name: "油费", category: "travel" },
|
||||
{ code: "travel_toll", name: "过路费", category: "travel" },
|
||||
{ code: "travel_parking", name: "停车费", category: "travel" },
|
||||
@@ -1088,5 +1093,7 @@ if (require.main === module) {
|
||||
|
||||
module.exports = {
|
||||
DEFAULT_APPROVAL_RULES,
|
||||
ENABLED_EXPENSE_TYPES,
|
||||
seedApprovalRules,
|
||||
seedExpenseTypes,
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ function buildTemplate(overrides = {}) {
|
||||
return {
|
||||
id: TEMPLATE_ID,
|
||||
name: "国内差旅报销",
|
||||
description: "机票、火车票与市内交通占位模板",
|
||||
description: "机票、火车票与大巴车票占位模板",
|
||||
items: [
|
||||
{
|
||||
expenseTypeId: EXPENSE_TYPE_ID,
|
||||
|
||||
@@ -60,7 +60,7 @@ function buildTemplate(overrides = {}) {
|
||||
},
|
||||
{
|
||||
expenseTypeId: BUS_TYPE_ID,
|
||||
description: "市内交通占位",
|
||||
description: "大巴车票占位",
|
||||
},
|
||||
],
|
||||
...overrides,
|
||||
@@ -127,7 +127,7 @@ async function buildApp(options = {}) {
|
||||
expenseTypes: options.expenseTypes ?? [
|
||||
{ id: AIRFARE_TYPE_ID, code: "travel_airfare", name: "机票", category: "travel", enabled: true },
|
||||
{ id: TRAIN_TYPE_ID, code: "travel_train", name: "火车票", category: "travel", enabled: true },
|
||||
{ id: BUS_TYPE_ID, code: "travel_bus", name: "市内交通", category: "travel", enabled: true },
|
||||
{ id: BUS_TYPE_ID, code: "travel_bus", name: "大巴车票", category: "travel", enabled: true },
|
||||
{ id: TAXI_TYPE_ID, code: "travel_taxi", name: "打车费", category: "travel", enabled: true },
|
||||
{ id: TRANSPORT_TYPE_ID, code: "transport", name: "legacy-交通费", category: "travel", enabled: true },
|
||||
],
|
||||
|
||||
@@ -1,8 +1,51 @@
|
||||
const {
|
||||
DEFAULT_APPROVAL_RULES,
|
||||
ENABLED_EXPENSE_TYPES,
|
||||
seedApprovalRules,
|
||||
seedExpenseTypes,
|
||||
} = require("../../../src/scripts/seed-config");
|
||||
|
||||
describe("default travel expense types", () => {
|
||||
it("keeps intercity bus tickets separate from the two travel allowances", () => {
|
||||
expect(ENABLED_EXPENSE_TYPES).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ code: "travel_bus", name: "大巴车票", category: "travel" }),
|
||||
expect.objectContaining({ code: "travel_meal", name: "伙食补助费", category: "travel" }),
|
||||
expect.objectContaining({
|
||||
code: "travel_local_transport_allowance",
|
||||
name: "市内交通补助费",
|
||||
category: "travel",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const travelCodes = ENABLED_EXPENSE_TYPES.filter((item) =>
|
||||
["travel_bus", "travel_meal", "travel_local_transport_allowance"].includes(item.code),
|
||||
);
|
||||
expect(travelCodes).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("upserts the distinct travel types by stable code", async () => {
|
||||
const upsert = vi.fn().mockResolvedValue({});
|
||||
|
||||
await seedExpenseTypes({ expenseType: { upsert } });
|
||||
|
||||
for (const [code, name] of [
|
||||
["travel_bus", "大巴车票"],
|
||||
["travel_meal", "伙食补助费"],
|
||||
["travel_local_transport_allowance", "市内交通补助费"],
|
||||
]) {
|
||||
expect(upsert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { code },
|
||||
update: expect.objectContaining({ name, enabled: true }),
|
||||
create: expect.objectContaining({ code, name, enabled: true }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("small-team default reimbursement approval rules", () => {
|
||||
it("uses finance for every owner fallback and finance review node", () => {
|
||||
expect(DEFAULT_APPROVAL_RULES).toEqual([
|
||||
|
||||
在新工单中引用
屏蔽一个用户