20 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 7232 | 费用报销(申请→审批→出纳付款)+ 出纳队列接通费用线 | admin | yst | 新增接口 | deployed | verified | verified | mmg | 860ad6bc | v2.1 | 2026-09-10 | verified 2026-09-10 mmg: expense.js 八端点+EXPENSE_STATUS 五态映射;expense/expense 列表页(状态/部门/公司/关键字筛选,分类末级按名传/公司按名传/部门手录注释,驳回 rejectReason 必填,详情按 REJECTED/PAID 条件显,操作列按状态机分叉);出纳 cashier 三端点扩容 EXPENSE 线随 #7217 一并交付(commit 7278c5f9)。expense.spec 锁端点/ID 透传/状态兜底。checkpoint 全绿。2026-09-10 后端更正:§10 出纳付款登记入参字段名勘误——付款账户/金额实为统一扁平 payAccountId/amount(此前误写输出字段 fundAccountId/actualAmount,前端据此调用触发 400)。2026-09-10 mmg 跟进修复(commit 860ad6bc):buildCashierPayPayload 删 EXPENSE 分叉统一 payAccountId/amount,CashierQueuePage 清理双键+EXPENSE 手续费恒 0 不传 fee,cashier.spec 断言翻转;checkpoint 全绿(含 Vitest 全量+生产构建)。此前联调记录的 cashier/pay EXPENSE 400 实为本文档笔误所致,非后端校验 bug。 | 2026-09-10 | dev-v3 |
费用报销(申请→审批→出纳付款)+ 出纳队列接通费用线
财务域新增费用报销子域:员工申请报销 → 财务审批(本期手工)→ 出纳付款 → 回写付讫。同时出纳域待付款队列 / 付款登记 / 已付款台账三个既有接口的入参枚举扩容,接通费用报销线(payType/bizType 新增 EXPENSE 值)。
- expense 新域路径前缀:
/admin/finance/expenses(8 个新端点) - cashier 既有域路径前缀:
/admin/finance/cashier(3 个端点入参枚举扩容,无字段增删)
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 |
|---|---|---|---|---|
| 1 | 报销分页 | GET | /admin/finance/expenses/page |
新增 |
| 2 | 申请报销 | POST | /admin/finance/expenses |
新增 |
| 3 | 报销详情 | GET | /admin/finance/expenses/{id} |
新增 |
| 4 | 编辑草稿 | PUT | /admin/finance/expenses/{id} |
新增 |
| 5 | 删除草稿 | DELETE | /admin/finance/expenses/{id} |
新增 |
| 6 | 提交报销 | PUT | /admin/finance/expenses/{id}/submit |
新增 |
| 7 | 批准报销 | PUT | /admin/finance/expenses/{id}/approve |
新增 |
| 8 | 驳回报销 | PUT | /admin/finance/expenses/{id}/reject |
新增 |
| 9 | 出纳待付款队列 | GET | /admin/finance/cashier/queue |
修改(入参 payType 扩容 EXPENSE) |
| 10 | 出纳付款登记 | POST | /admin/finance/cashier/pay |
修改(入参 bizType 扩容 EXPENSE) |
| 11 | 出纳已付款台账 | GET | /admin/finance/cashier/payments/page |
修改(入参 bizType 扩容 EXPENSE) |
三、接口详情
1. 报销分页 GET /admin/finance/expenses/page
使用场景
管理后台「费用报销」列表页,按状态 / 部门 / 公司主体 / 关键字筛选分页查询。
认证 / 幂等性 / 限流
- 认证:管理后台管理员 Token(
Authorization: Bearer <admin-token>),未登录返 401。 - 幂等性:查询接口,天然幂等。
- 限流:走网关默认限流,无特殊配置。
入参(Query)
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
status |
Query | String | 否 | 见状态枚举 | 按报销状态筛选,省略查全部 |
departmentId |
Query | Number | 否 | 正整数 | 按部门 ID 筛选 |
company |
Query | String | 否 | — | 按公司主体名称筛选 |
keyword |
Query | String | 否 | — | 关键字模糊搜索(单号 / 事由) |
page |
Query | Number | 是 | ≥1 | 页码 |
pageSize |
Query | Number | 是 | 1-100 | 每页条数 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
data.records[].id |
String | 报销单 ID(雪花 ID,Long 转字符串防 JS 精度丢失) |
data.records[].expenseNo |
String | 报销单号(FY- 前缀) |
data.records[].expenseCategory |
String | 费用分类末级名称 |
data.records[].departmentId |
String | 部门 ID |
data.records[].departmentName |
String | 部门名称 |
data.records[].company |
String | 公司主体名称 |
data.records[].amount |
Number | 报销金额 |
data.records[].offsetLoanAmt |
Number | 冲销借款金额(本期恒 0,见业务边界) |
data.records[].payableAmt |
Number | 应付金额(本期 = amount) |
data.records[].reason |
String | 报销事由 |
data.records[].rejectReason |
String | 驳回原因(仅 REJECTED 状态有值,其余为 null) |
data.records[].fundAccountId |
String | 付讫资金账户 ID(仅 PAID 有值) |
data.records[].status |
String | 状态码,见状态枚举 |
data.records[].paidAt |
String | 付讫时间(仅 PAID 有值) |
data.records[].approvalInstanceId |
String | 审批实例 ID(本期恒空,企微审批未接) |
data.records[].createTime |
String | 创建时间 |
data.total / page / pageSize |
Number | 分页元信息 |
请求示例(典型)
GET /admin/finance/expenses/page?page=1&pageSize=20&status=APPROVED&departmentId=10086
Authorization: Bearer <admin-token>
响应示例(典型)
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"records": [
{
"id": "2094278854020399106",
"expenseNo": "FY-20260907-0001",
"expenseCategory": "市内交通费",
"departmentId": "10086",
"departmentName": "市场部",
"company": "呼籁旅行社有限公司",
"amount": 300.00,
"offsetLoanAmt": 0,
"payableAmt": 300.00,
"reason": "客户拜访打车费",
"rejectReason": null,
"fundAccountId": null,
"status": "APPROVED",
"paidAt": null,
"approvalInstanceId": null,
"createTime": "2026-09-07 10:00:00"
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
}
空数据 / 边界响应
无匹配时 records=[]、total=0,HTTP 200,前端正常渲染空列表。
2. 申请报销 POST /admin/finance/expenses
VO: ExpenseCreateReqVO
使用场景
员工在管理后台提交一张费用报销单,落库为 PENDING 草稿(未进审批)。
认证 / 幂等性 / 限流
- 认证:管理后台管理员 Token。
- 幂等性:非幂等(每次创建生成新单号),重复提交产生多张单据,前端提交后应禁用按钮。
- 限流:网关默认。
入参(Body)
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
expenseCategory |
String | 是 | 必须存在、为末级(level=2)、未停用 | 费用分类末级名称,按名称传不按 ID(取自 fin_expense_category 字典) |
departmentId |
Number | 是 | 必须存在 | 部门 ID(用户域组织树 wechat_department) |
company |
String | 是 | 必须为启用项 | 公司主体名称,按名称传(取自 travel_agency 启用项) |
amount |
Number | 是 | >0 | 报销金额 |
reason |
String | 是 | 非空 | 报销事由 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
data.id |
String | 新建报销单 ID |
data.expenseNo |
String | 报销单号(FY- 前缀) |
data.status |
String | 创建后为 PENDING |
请求示例(典型成功)
{
"expenseCategory": "市内交通费",
"departmentId": 10086,
"company": "呼籁旅行社有限公司",
"amount": 300.00,
"reason": "客户拜访打车费"
}
请求示例(边界情况)
金额为最小粒度 / 事由最长:
{
"expenseCategory": "办公用品费",
"departmentId": 10086,
"company": "呼籁旅行社有限公司",
"amount": 0.01,
"reason": "采购 A4 打印纸一箱"
}
请求示例(业务失败)
费用分类传了非末级 / 已停用名称 → 598703:
{
"expenseCategory": "交通费",
"departmentId": 10086,
"company": "呼籁旅行社有限公司",
"amount": 300.00,
"reason": "打车"
}
{"code":598703,"message":"费用分类非法(不存在/非末级/已停用)","success":false,"data":null}
金额 ≤0 → 598704;部门或公司主体非法 → 598705。
3. 报销详情 GET /admin/finance/expenses/{id}
入参
| 字段 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
id |
Path | String | 是 | 报销单 ID(雪花字符串) |
出参
字段与分页行一致(id/expenseNo/expenseCategory/departmentId/departmentName/company/amount/offsetLoanAmt/payableAmt/reason/rejectReason/fundAccountId/status/paidAt/approvalInstanceId/createTime),见 §1 出参表。
错误响应
{"code":598701,"message":"报销单不存在","success":false,"data":null}
4. 编辑草稿 PUT /admin/finance/expenses/{id}
VO: ExpenseUpdateReqVO(字段与 ExpenseCreateReqVO 完全一致,见 §2 入参表)
业务边界
- 仅
PENDING状态可编辑,其余状态调用返回 598702。 - 编辑不改单号,状态保持 PENDING。
请求示例
{
"expenseCategory": "市内交通费",
"departmentId": 10086,
"company": "呼籁旅行社有限公司",
"amount": 350.00,
"reason": "客户拜访打车费(含返程)"
}
错误响应
{"code":598702,"message":"报销单状态非法(仅PENDING可编辑删除、提交须PENDING、审批须SUBMITTED)","success":false,"data":null}
5. 删除草稿 DELETE /admin/finance/expenses/{id}
业务边界
- 仅
PENDING状态可删除,其余状态调用返回 598702。 - 删除为软删除,单据不再出现在任何列表。
出参
| 字段 | 类型 | 说明 |
|---|---|---|
data |
Boolean | true 表示删除成功 |
6. 提交报销 PUT /admin/finance/expenses/{id}/submit
业务边界
- 仅
PENDING状态可提交,提交后流转PENDING → SUBMITTED,单据进入锁定态(不可编辑 / 删除)。 - 无 Body 入参。
出参
| 字段 | 类型 | 说明 |
|---|---|---|
data.id |
String | 报销单 ID |
data.status |
String | 提交后为 SUBMITTED |
7. 批准报销 PUT /admin/finance/expenses/{id}/approve
业务边界
- 仅
SUBMITTED状态可批准,流转SUBMITTED → APPROVED,进入出纳待付款队列。 - 本期为手工审批(不接企微审批流),
approvalInstanceId恒空。 - 无 Body 入参。
出参
| 字段 | 类型 | 说明 |
|---|---|---|
data.id |
String | 报销单 ID |
data.status |
String | 批准后为 APPROVED |
8. 驳回报销 PUT /admin/finance/expenses/{id}/reject
入参(Body)
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
rejectReason |
String | 是 | 非空 | 驳回原因(回显在详情 rejectReason 字段) |
业务边界
- 仅
SUBMITTED状态可驳回,流转SUBMITTED → REJECTED,不进出纳队列。
请求示例
{"rejectReason": "缺少打车发票附件,请补充后重新提交"}
出参
| 字段 | 类型 | 说明 |
|---|---|---|
data.id |
String | 报销单 ID |
data.status |
String | 驳回后为 REJECTED |
9. 出纳待付款队列 GET /admin/finance/cashier/queue(修改:payType 扩容)
变更点
入参 payType 枚举由仅 NONBIZ 扩容为 NONBIZ / EXPENSE。传 EXPENSE 时返回费用报销线(fin_expense 表 status=APPROVED 的单据);其余字段、结构、分页契约不变。
入参(Query,增量)
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
payType |
Query | String | 否 | NONBIZ / EXPENSE |
付款类型筛选;省略 / 空 = 既有行为 |
出参(队列行,费用线字段填充规则)
| 字段 | 类型 | 费用线(payType=EXPENSE)取值 |
|---|---|---|
id |
String | 报销单 ID |
bizNo |
String | 报销单号(= expenseNo) |
payType |
String | EXPENSE |
payTypeName |
String | 费用 |
unitId / unitName |
String | 槽位复用:部门 ID / 部门名称 |
category / categoryName |
String | 费用分类(末级名称) |
amount |
Number | 报销金额 |
fee |
Number | 手续费(费用线为 0) |
actualAmount |
Number | 应付金额(= payableAmt) |
operatorName |
String | 申请人 |
createTime |
String | 创建时间 |
occurDate |
String | 发生日期 |
status |
String | 队列状态 |
remark |
String | 报销事由(= reason) |
请求示例
GET /admin/finance/cashier/queue?payType=EXPENSE
Authorization: Bearer <admin-token>
10. 出纳付款登记 POST /admin/finance/cashier/pay(修改:bizType 扩容)
变更点
入参 bizType 枚举新增 EXPENSE 值。bizType=EXPENSE 时 bizId 传报销单 ID,登记付款后回写 fin_expense 状态为 PAID、记录 fundAccountId / paidAt(回写列名,非入参字段),同事务记资金流水。
入参(Body,关键字段)
⚠️ 出纳付款登记为全 bizType 统一扁平入参(NONBIZ / EXPENSE / PAYMENT / PREPAY 共用同一套字段),付款账户 / 金额字段名固定为
payAccountId/amount。下方为全量必填/关键字段:
| 字段 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|
bizType |
String | 是 | 新增 EXPENSE |
业务类型;费用报销传 EXPENSE |
bizId |
String | 是 | 对应业务单据 ID | bizType=EXPENSE 时传报销单 ID |
payAccountId |
String | 是 | 必须存在且 ACTIVE | 出账资金账户 ID(fin_fund_account) |
amount |
Number | 是 | >0 | 付款金额(出纳按实际打款金额录入,可与应付不一致) |
payDate |
String | 是 | yyyy-MM-dd | 付款日期(可回溯补录) |
payMethod |
String | 否 | 字典 fin_pay_way | 付款方式(CASH/BANK_TRANSFER/WECHAT/ALIPAY) |
fee |
Number | 否 | ≥0 | 手续费(挂出账流水) |
voucherNo |
String | 否 | — | 付款凭证号 |
voucherUrl |
String | 否 | — | 付款凭证影像 URL |
请求示例(bizType=EXPENSE)
{
"bizType": "EXPENSE",
"bizId": "2097885645098659841",
"payAccountId": "2095340438490583041",
"amount": 300.00,
"payDate": "2026-09-10",
"payMethod": "BANK_TRANSFER"
}
成功响应
{
"code": 200,
"message": "成功",
"data": { "flowId": "2097946971271495682", "flowNo": "LS202609100002", "balanceAfter": 114594.0, "bizId": "2097885645098659841" },
"success": true
}
业务边界
- 出纳实际打款金额与
payableAmt不一致时后端打 WARN 审计日志但不硬拦(允许出纳按实际打款登记)。 - 并发防重:同事务记资金流水 + 条件更新回写,对同一报销单重复付款返回 598602。
错误响应
重复付款:
{"code":598602,"message":"业务单状态非已批准,不可付款","success":false,"data":null}
付款类型非法(598607 文案已扩为含 EXPENSE):
{"code":598607,"message":"付款类型非法,本期支持 NONBIZ/EXPENSE","success":false,"data":null}
11. 出纳已付款台账 GET /admin/finance/cashier/payments/page(修改:bizType 扩容)
变更点
入参新增 bizType 筛选,支持 EXPENSE;传空 / 省略 = 既有 NONBIZ 行为。其余字段、结构不变。
入参(Query,增量)
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
bizType |
Query | String | 否 | NONBIZ / EXPENSE |
业务类型筛选;空 = NONBIZ(既有行为) |
请求示例
GET /admin/finance/cashier/payments/page?page=1&pageSize=20&bizType=EXPENSE
Authorization: Bearer <admin-token>
六.5、枚举 / 数据字典
status(报销单状态,代码枚举)
所属字段: ExpensePageReqVO.status / ExpenseRespVO.status | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
PENDING |
待提交 | 草稿态,可编辑可删除 |
SUBMITTED |
审批中 | 已提交,锁定(不可编辑 / 删除) |
APPROVED |
已批准 | 审批通过,待出纳付款 |
REJECTED |
已驳回 | 审批驳回,rejectReason 有值 |
PAID |
已付讫 | 出纳付款完成,终态只读 |
状态机:PENDING →(submit)→ SUBMITTED →(approve)→ APPROVED →(出纳付款)→ PAID;SUBMITTED →(reject)→ REJECTED。
payType / bizType(出纳域,枚举值扩容)
所属字段: cashier/queue 入参 payType、cashier/pay 入参 bizType、cashier/payments/page 入参 bizType | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
NONBIZ |
非业务付款 | 既有值,行为不变 |
EXPENSE |
费用(报销) | 新增值,费用报销线 |
expenseCategory(费用分类,数据字典 fin_expense_category)
- 按末级名称(level=2)传值,按名称不按 ID。
- 仅正常(未停用)项可选;传非末级 / 不存在 / 已停用名称返回 598703。
company(公司主体,数据字典 travel_agency)
- 按名称传值,仅启用项可选;非法返回 598705。
错误码(新段 5987xx + 出纳段扩充)
| 错误码 | 含义 | 触发场景 |
|---|---|---|
| 598701 | 报销单不存在 | 详情 / 编辑 / 删除 / 提交 / 审批传错 ID |
| 598702 | 报销单状态非法 | 非 PENDING 编辑删除、非 PENDING 提交、非 SUBMITTED 审批 |
| 598703 | 费用分类非法 | 分类不存在 / 非末级 / 已停用 |
| 598704 | 金额无效 | amount ≤ 0 |
| 598705 | 部门或公司主体非法 | departmentId 不存在或 company 非启用项 |
| 598706 | 单号取号撞号重试耗尽 | 极端并发下取号失败(重试后仍撞号) |
| 598607 | 付款类型非法(文案扩充) | 出纳付款 / 台账传非 NONBIZ/EXPENSE 值 |
六.6、修改前后对比
| 项目 | 修改前 | 修改后 |
|---|---|---|
| 费用报销接口 | 无 | 新增 /admin/finance/expenses 下 8 端点(分页 / 申请 / 详情 / 编辑 / 删除 / 提交 / 批准 / 驳回) |
出纳队列 payType |
仅 NONBIZ |
扩容 NONBIZ / EXPENSE |
出纳付款 bizType |
仅 NONBIZ |
扩容 NONBIZ / EXPENSE |
| 出纳台账筛选 | 无 bizType 入参 |
新增 bizType 入参(空 = NONBIZ) |
| 598607 文案 | 仅提示 NONBIZ | 扩为「付款类型非法,本期支持 NONBIZ/EXPENSE」 |
六.7、影响评估
- 是否破坏向后兼容:否。expense 8 端点为纯新增;出纳 3 端点为入参枚举扩容,省略新值时行为与之前完全一致。
- 前端是否必须同步上线:否。旧功能不受影响;费用报销页面与出纳队列费用线 Tab 可按节奏上线。
- 前端 workaround 清理点:无(全新能力)。
七、不影响范围
- 小程序端(C 端):费用报销为纯管理后台能力,
/mp/**零改动。 - 出纳 NONBIZ 线:不传
payType/bizType=EXPENSE时行为与之前完全一致。 - 员工借款 / 冲销:本期未建借款域,
offsetLoanAmt恒 0、payableAmt = amount,留 TODO 后续接入。
八、测试环境已验证
192.168.100.236 测试服行为级 E2E 16 PASS / 0 FAIL:
- 全链路走通:申请 → 提交 → 批准 → 出纳付款 → 回写 PAID。
- 结存勾稽正确:付款前资金账户余额 114894 − 打款 300 = 114594。
- 并发防重生效:重复付款返回 598602。
- 四类校验生效:598702(状态非法)/ 598703(分类非法)/ 598704(金额无效)/ 598705(部门或公司非法)。
当前状态
- 后端:已部署并已验证。
- 前端:待处理(费用报销页面 + 出纳队列费用线 Tab)。